Bring in the new iSCSI target and initiator.

Reviewed by:	ken (parts)
Approved by:	re (delphij)
Sponsored by:	FreeBSD Foundation
This commit is contained in:
Edward Tomasz Napierala 2013-09-14 15:29:06 +00:00
parent 196beb5359
commit 009ea47eb2
60 changed files with 19160 additions and 7 deletions

View File

@ -263,9 +263,13 @@ syslogd_flags="-s" # Flags to syslogd (if enabled).
inetd_enable="NO" # Run the network daemon dispatcher (YES/NO).
inetd_program="/usr/sbin/inetd" # path to inetd, if you want a different one.
inetd_flags="-wW -C 60" # Optional flags to inetd
iscsid_enable="NO" # iSCSI initiator daemon.
iscsictl_enable="NO" # iSCSI initiator autostart.
iscsictl_flags="-Aa" # Optional flags to iscsictl.
hastd_enable="NO" # Run the HAST daemon (YES/NO).
hastd_program="/sbin/hastd" # path to hastd, if you want a different one.
hastd_flags="" # Optional flags to hastd.
ctld_enable="NO" # CAM Target Layer / iSCSI target daemon.
#
# named. It may be possible to run named in a sandbox, man security for
# details.

View File

@ -30,6 +30,7 @@ FILES= DAEMON \
cleanvar \
cleartmp \
cron \
ctld \
ddb \
defaultroute \
devd \
@ -62,6 +63,8 @@ FILES= DAEMON \
ipnat \
ipsec \
${_ipxrouted} \
iscsictl \
iscsid \
jail \
kadmind \
kerberos \

22
etc/rc.d/ctld Executable file
View File

@ -0,0 +1,22 @@
#!/bin/sh
#
# $FreeBSD$
#
# PROVIDE: ctld
# REQUIRE: FILESYSTEMS
# BEFORE: DAEMON
# KEYWORD: nojail
. /etc/rc.subr
name="ctld"
rcvar="ctld_enable"
pidfile="/var/run/${name}.pid"
command="/usr/sbin/${name}"
required_files="/etc/ctl.conf"
required_modules="ctl"
extra_commands="reload"
load_rc_config $name
run_rc_command "$1"

20
etc/rc.d/iscsictl Executable file
View File

@ -0,0 +1,20 @@
#!/bin/sh
#
# $FreeBSD$
#
# PROVIDE: iscsictl
# REQUIRE: NETWORK iscsid
# BEFORE: DAEMON
# KEYWORD: nojail
. /etc/rc.subr
name="iscsictl"
rcvar="iscsictl_enable"
command="/usr/bin/${name}"
command_args="${iscsictl_flags}"
required_modules="iscsi"
load_rc_config $name
run_rc_command "$1"

20
etc/rc.d/iscsid Executable file
View File

@ -0,0 +1,20 @@
#!/bin/sh
#
# $FreeBSD$
#
# PROVIDE: iscsid
# REQUIRE: NETWORK
# BEFORE: DAEMON
# KEYWORD: nojail
. /etc/rc.subr
name="iscsid"
rcvar="iscsid_enable"
pidfile="/var/run/${name}.pid"
command="/usr/sbin/${name}"
required_modules="iscsi"
load_rc_config $name
run_rc_command "$1"

View File

@ -112,6 +112,7 @@ whatever options are specified, and start an iscsi-session.
.Xr iscsi_initiator 4 ,
.Xr sa 4 ,
.Xr iscsi.conf 5 ,
.Xr iscsictl 8 ,
.Xr camcontrol 8
.Sh STANDARDS
RFC 3720

View File

@ -201,6 +201,7 @@ The parsing is very primitive, so do not expect - at the moment - any
error messages.
.Sh SEE ALSO
.Xr iscsi_initiator 4 ,
.Xr iscsictl 8 ,
.Xr iscontrol 8
.Sh STANDARDS
ISCSI RFC 3720

View File

@ -75,8 +75,11 @@ Error injection support
.It
All I/O handled in-kernel, no userland context switch overhead
.El
.Pp
It also serves as a kernel component of the native iSCSI target.
.Sh SEE ALSO
.Xr ctladm 8 ,
.Xr ctld 8 ,
.Xr ctlstat 8
.Sh HISTORY
The

View File

@ -3148,6 +3148,28 @@ ctl_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag,
sbuf_delete(sb);
break;
}
case CTL_ISCSI: {
struct ctl_iscsi *ci;
struct ctl_frontend *fe;
ci = (struct ctl_iscsi *)addr;
mtx_lock(&softc->ctl_lock);
STAILQ_FOREACH(fe, &softc->fe_list, links) {
if (strcmp(fe->port_name, "iscsi") == 0)
break;
}
mtx_unlock(&softc->ctl_lock);
if (fe == NULL) {
ci->status = CTL_ISCSI_ERROR;
snprintf(ci->error_str, sizeof(ci->error_str), "Backend \"iscsi\" not found.");
break;
}
retval = fe->ioctl(dev, cmd, addr, flag, td);
break;
}
default: {
/* XXX KDM should we fix this? */
#if 0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,112 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef CTL_FRONTEND_ISCSI_H
#define CTL_FRONTEND_ISCSI_H
struct cfiscsi_target {
TAILQ_ENTRY(cfiscsi_target) ct_next;
int ct_luns[CTL_MAX_LUNS];
struct cfiscsi_softc *ct_softc;
volatile u_int ct_refcount;
char ct_name[CTL_ISCSI_NAME_LEN];
char ct_alias[CTL_ISCSI_ALIAS_LEN];
};
struct cfiscsi_data_wait {
TAILQ_ENTRY(cfiscsi_data_wait) cdw_next;
union ctl_io *cdw_ctl_io;
uint32_t cdw_target_transfer_tag;
uint32_t cdw_initiator_task_tag;
int cdw_sg_index;
char *cdw_sg_addr;
size_t cdw_sg_len;
};
#define CFISCSI_SESSION_STATE_INVALID 0
#define CFISCSI_SESSION_STATE_BHS 1
#define CFISCSI_SESSION_STATE_AHS 2
#define CFISCSI_SESSION_STATE_HEADER_DIGEST 3
#define CFISCSI_SESSION_STATE_DATA 4
#define CFISCSI_SESSION_STATE_DATA_DIGEST 5
struct cfiscsi_session {
TAILQ_ENTRY(cfiscsi_session) cs_next;
struct mtx cs_lock;
struct icl_conn *cs_conn;
uint32_t cs_cmdsn;
uint32_t cs_statsn;
uint32_t cs_target_transfer_tag;
volatile u_int cs_outstanding_ctl_pdus;
TAILQ_HEAD(, cfiscsi_data_wait) cs_waiting_for_data_out;
struct cfiscsi_target *cs_target;
struct callout cs_callout;
int cs_timeout;
int cs_portal_group_tag;
struct cv cs_maintenance_cv;
int cs_terminating;
size_t cs_max_data_segment_length;
size_t cs_max_burst_length;
bool cs_immediate_data;
char cs_initiator_name[CTL_ISCSI_NAME_LEN];
char cs_initiator_addr[CTL_ISCSI_ADDR_LEN];
char cs_initiator_alias[CTL_ISCSI_ALIAS_LEN];
unsigned int cs_id;
int cs_ctl_initid;
#ifdef ICL_KERNEL_PROXY
bool cs_login_phase;
bool cs_waiting_for_ctld;
struct cv cs_login_cv;
struct icl_pdu *cs_login_pdu;
#endif
};
#ifdef ICL_KERNEL_PROXY
struct icl_listen;
#endif
struct cfiscsi_softc {
struct ctl_frontend fe;
struct mtx lock;
char port_name[32];
int online;
unsigned int last_session_id;
TAILQ_HEAD(, cfiscsi_target) targets;
TAILQ_HEAD(, cfiscsi_session) sessions;
char ctl_initids[CTL_MAX_INIT_PER_PORT];
int max_initiators;
#ifdef ICL_KERNEL_PROXY
struct icl_listen *listener;
struct cv accept_cv;
#endif
};
#endif /* !CTL_FRONTEND_ISCSI_H */

View File

@ -40,6 +40,12 @@
#ifndef _CTL_IOCTL_H_
#define _CTL_IOCTL_H_
#ifdef ICL_KERNEL_PROXY
#include <sys/socket.h>
#endif
#include <sys/ioccom.h>
#define CTL_DEFAULT_DEV "/dev/cam/ctl"
/*
* Maximum number of targets we support.
@ -588,6 +594,168 @@ struct ctl_lun_list {
/* passed to userland */
};
/*
* iSCSI status
*
* OK: Request completed successfully.
*
* ERROR: An error occured, look at the error string for a
* description of the error.
*
* CTL_ISCSI_LIST_NEED_MORE_SPACE:
* User has to pass larger buffer for CTL_ISCSI_LIST ioctl.
*/
typedef enum {
CTL_ISCSI_OK,
CTL_ISCSI_ERROR,
CTL_ISCSI_LIST_NEED_MORE_SPACE,
CTL_ISCSI_SESSION_NOT_FOUND
} ctl_iscsi_status;
typedef enum {
CTL_ISCSI_HANDOFF,
CTL_ISCSI_LIST,
CTL_ISCSI_LOGOUT,
CTL_ISCSI_TERMINATE,
#ifdef ICL_KERNEL_PROXY
CTL_ISCSI_LISTEN,
CTL_ISCSI_ACCEPT,
CTL_ISCSI_SEND,
CTL_ISCSI_RECEIVE,
CTL_ISCSI_CLOSE,
#endif
} ctl_iscsi_type;
typedef enum {
CTL_ISCSI_DIGEST_NONE,
CTL_ISCSI_DIGEST_CRC32C
} ctl_iscsi_digest;
#define CTL_ISCSI_NAME_LEN 224 /* 223 bytes, by RFC 3720, + '\0' */
#define CTL_ISCSI_ADDR_LEN 47 /* INET6_ADDRSTRLEN + '\0' */
#define CTL_ISCSI_ALIAS_LEN 128 /* Arbitrary. */
struct ctl_iscsi_handoff_params {
char initiator_name[CTL_ISCSI_NAME_LEN];
char initiator_addr[CTL_ISCSI_ADDR_LEN];
char initiator_alias[CTL_ISCSI_ALIAS_LEN];
char target_name[CTL_ISCSI_NAME_LEN];
#ifdef ICL_KERNEL_PROXY
int connection_id;
/*
* XXX
*/
int socket;
#else
int socket;
#endif
int portal_group_tag;
/*
* Connection parameters negotiated by ctld(8).
*/
ctl_iscsi_digest header_digest;
ctl_iscsi_digest data_digest;
uint32_t cmdsn;
uint32_t statsn;
uint32_t max_recv_data_segment_length;
uint32_t max_burst_length;
uint32_t first_burst_length;
uint32_t immediate_data;
};
struct ctl_iscsi_list_params {
uint32_t alloc_len; /* passed to kernel */
char *conn_xml; /* filled in kernel */
uint32_t fill_len; /* passed to userland */
};
struct ctl_iscsi_logout_params {
int connection_id; /* passed to kernel */
char initiator_name[CTL_ISCSI_NAME_LEN];
/* passed to kernel */
char initiator_addr[CTL_ISCSI_ADDR_LEN];
/* passed to kernel */
int all; /* passed to kernel */
};
struct ctl_iscsi_terminate_params {
int connection_id; /* passed to kernel */
char initiator_name[CTL_ISCSI_NAME_LEN];
/* passed to kernel */
char initiator_addr[CTL_ISCSI_NAME_LEN];
/* passed to kernel */
int all; /* passed to kernel */
};
#ifdef ICL_KERNEL_PROXY
struct ctl_iscsi_listen_params {
int iser;
int domain;
int socktype;
int protocol;
struct sockaddr *addr;
socklen_t addrlen;
};
struct ctl_iscsi_accept_params {
int connection_id;
};
struct ctl_iscsi_send_params {
int connection_id;
void *bhs;
size_t spare;
void *spare2;
size_t data_segment_len;
void *data_segment;
};
struct ctl_iscsi_receive_params {
int connection_id;
void *bhs;
size_t spare;
void *spare2;
size_t data_segment_len;
void *data_segment;
};
struct ctl_iscsi_close_params {
int connection_id;
};
#endif /* ICL_KERNEL_PROXY */
union ctl_iscsi_data {
struct ctl_iscsi_handoff_params handoff;
struct ctl_iscsi_list_params list;
struct ctl_iscsi_logout_params logout;
struct ctl_iscsi_terminate_params terminate;
#ifdef ICL_KERNEL_PROXY
struct ctl_iscsi_listen_params listen;
struct ctl_iscsi_accept_params accept;
struct ctl_iscsi_send_params send;
struct ctl_iscsi_receive_params receive;
struct ctl_iscsi_close_params close;
#endif
};
/*
* iSCSI interface
*
* status: The status of the request. See above for the
* description of the values of this field.
*
* error_str: If the status indicates an error, this string will
* be filled in to describe the error.
*/
struct ctl_iscsi {
ctl_iscsi_type type; /* passed to kernel */
union ctl_iscsi_data data; /* passed to kernel */
ctl_iscsi_status status; /* passed to userland */
char error_str[CTL_ERROR_STR_LEN];
/* passed to userland */
};
#define CTL_IO _IOWR(CTL_MINOR, 0x00, union ctl_io)
#define CTL_ENABLE_PORT _IOW(CTL_MINOR, 0x04, struct ctl_port_entry)
#define CTL_DISABLE_PORT _IOW(CTL_MINOR, 0x05, struct ctl_port_entry)
@ -612,6 +780,7 @@ struct ctl_lun_list {
#define CTL_LUN_LIST _IOWR(CTL_MINOR, 0x22, struct ctl_lun_list)
#define CTL_ERROR_INJECT_DELETE _IOW(CTL_MINOR, 0x23, struct ctl_error_desc)
#define CTL_SET_PORT_WWNS _IOW(CTL_MINOR, 0x24, struct ctl_port_entry)
#define CTL_ISCSI _IOWR(CTL_MINOR, 0x25, struct ctl_iscsi)
#endif /* _CTL_IOCTL_H_ */

View File

@ -124,6 +124,7 @@ cam/ctl/ctl_cmd_table.c optional ctl
cam/ctl/ctl_frontend.c optional ctl
cam/ctl/ctl_frontend_cam_sim.c optional ctl
cam/ctl/ctl_frontend_internal.c optional ctl
cam/ctl/ctl_frontend_iscsi.c optional ctl
cam/ctl/ctl_mem_pool.c optional ctl
cam/ctl/ctl_scsi_all.c optional ctl
cam/ctl/ctl_error.c optional ctl
@ -1531,6 +1532,9 @@ ipw_monitor.fw optional ipwmonitorfw | ipwfw \
compile-with "${NORMAL_FW}" \
no-obj no-implicit-rule \
clean "ipw_monitor.fw"
dev/iscsi/icl.c optional iscsi | ctl
dev/iscsi/icl_proxy.c optional iscsi | ctl
dev/iscsi/iscsi.c optional iscsi scbus
dev/iscsi_initiator/iscsi.c optional iscsi_initiator scbus
dev/iscsi_initiator/iscsi_subr.c optional iscsi_initiator scbus
dev/iscsi_initiator/isc_cam.c optional iscsi_initiator scbus

1292
sys/dev/iscsi/icl.c Normal file

File diff suppressed because it is too large Load Diff

151
sys/dev/iscsi/icl.h Normal file
View File

@ -0,0 +1,151 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef ICL_H
#define ICL_H
/*
* iSCSI Common Layer. It's used by both the initiator and target to send
* and receive iSCSI PDUs.
*/
struct icl_conn;
struct icl_pdu {
TAILQ_ENTRY(icl_pdu) ip_next;
struct icl_conn *ip_conn;
struct iscsi_bhs *ip_bhs;
struct mbuf *ip_bhs_mbuf;
size_t ip_ahs_len;
struct mbuf *ip_ahs_mbuf;
size_t ip_data_len;
struct mbuf *ip_data_mbuf;
/*
* User (initiator or provider) private fields.
*/
uint32_t ip_prv0;
uint32_t ip_prv1;
uint32_t ip_prv2;
};
struct icl_pdu *icl_pdu_new_bhs(struct icl_conn *ic, int flags);
size_t icl_pdu_data_segment_length(const struct icl_pdu *ip);
int icl_pdu_append_data(struct icl_pdu *ip, const void *addr, size_t len, int flags);
void icl_pdu_get_data(struct icl_pdu *ip, size_t off, void *addr, size_t len);
void icl_pdu_queue(struct icl_pdu *ip);
void icl_pdu_free(struct icl_pdu *ip);
#define ICL_CONN_STATE_INVALID 0
#define ICL_CONN_STATE_BHS 1
#define ICL_CONN_STATE_AHS 2
#define ICL_CONN_STATE_HEADER_DIGEST 3
#define ICL_CONN_STATE_DATA 4
#define ICL_CONN_STATE_DATA_DIGEST 5
#define ICL_MAX_DATA_SEGMENT_LENGTH (128 * 1024)
struct icl_conn {
struct mtx ic_lock;
struct socket *ic_socket;
volatile u_int ic_outstanding_pdus;
TAILQ_HEAD(, icl_pdu) ic_to_send;
size_t ic_receive_len;
int ic_receive_state;
struct icl_pdu *ic_receive_pdu;
struct cv ic_send_cv;
struct cv ic_receive_cv;
bool ic_header_crc32c;
bool ic_data_crc32c;
bool ic_send_running;
bool ic_receive_running;
size_t ic_max_data_segment_length;
bool ic_disconnecting;
bool ic_iser;
void (*ic_receive)(struct icl_pdu *);
void (*ic_error)(struct icl_conn *);
/*
* User (initiator or provider) private fields.
*/
void *ic_prv0;
};
struct icl_conn *icl_conn_new(void);
void icl_conn_free(struct icl_conn *ic);
int icl_conn_handoff(struct icl_conn *ic, int fd);
void icl_conn_shutdown(struct icl_conn *ic);
void icl_conn_close(struct icl_conn *ic);
bool icl_conn_connected(struct icl_conn *ic);
#ifdef ICL_KERNEL_PROXY
struct sockaddr;
struct icl_listen;
struct icl_listen_sock {
TAILQ_ENTRY(icl_listen_sock) ils_next;
struct icl_listen *ils_listen;
struct socket *ils_socket;
bool ils_running;
bool ils_disconnecting;
};
struct icl_listen {
TAILQ_HEAD(, icl_listen_sock) il_sockets;
struct sx il_lock;
void (*il_accept)(struct socket *);
};
/*
* Initiator part.
*/
int icl_conn_connect(struct icl_conn *ic, bool rdma,
int domain, int socktype, int protocol,
struct sockaddr *from_sa, struct sockaddr *to_sa);
/*
* Target part.
*/
struct icl_listen *icl_listen_new(void (*accept_cb)(struct socket *));
void icl_listen_free(struct icl_listen *il);
int icl_listen_add(struct icl_listen *il, bool rdma, int domain,
int socktype, int protocol, struct sockaddr *sa);
int icl_listen_remove(struct icl_listen *il, struct sockaddr *sa);
/*
* This one is not a public API; only to be used by icl_proxy.c.
*/
int icl_conn_handoff_sock(struct icl_conn *ic, struct socket *so);
#endif /* ICL_KERNEL_PROXY */
#endif /* !ICL_H */

397
sys/dev/iscsi/icl_proxy.c Normal file
View File

@ -0,0 +1,397 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
/*-
* Copyright (c) 1982, 1986, 1989, 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* sendfile(2) and related extensions:
* Copyright (c) 1998, David Greenman. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)uipc_syscalls.c 8.4 (Berkeley) 2/21/94
*/
/*
* iSCSI Common Layer, kernel proxy part.
*/
#ifdef ICL_KERNEL_PROXY
#include <sys/param.h>
#include <sys/capability.h>
#include <sys/condvar.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sx.h>
#include <sys/systm.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <linux/types.h>
#include <rdma/rdma_cm.h>
#include "icl.h"
static int debug = 1;
#define ICL_DEBUG(X, ...) \
if (debug > 1) { \
printf("%s: " X "\n", __func__, ## __VA_ARGS__);\
} while (0)
#define ICL_WARN(X, ...) \
if (debug > 0) { \
printf("WARNING: %s: " X "\n", \
__func__, ## __VA_ARGS__); \
} while (0)
static MALLOC_DEFINE(M_ICL_PROXY, "ICL_PROXY", "iSCSI common layer proxy");
#ifdef ICL_RDMA
static int icl_conn_connect_rdma(struct icl_conn *ic, int domain, int socktype,
int protocol, struct sockaddr *from_sa, struct sockaddr *to_sa);
static int icl_listen_add_rdma(struct icl_listen *il, int domain, int socktype, int protocol,
struct sockaddr *sa);
#endif /* ICL_RDMA */
static int
icl_conn_connect_tcp(struct icl_conn *ic, int domain, int socktype,
int protocol, struct sockaddr *from_sa, struct sockaddr *to_sa)
{
struct socket *so;
int error;
int interrupted = 0;
error = socreate(domain, &so, socktype, protocol,
curthread->td_ucred, curthread);
if (error != 0)
return (error);
if (from_sa != NULL) {
error = sobind(so, from_sa, curthread);
if (error != 0) {
soclose(so);
return (error);
}
}
error = soconnect(so, to_sa, curthread);
if (error != 0) {
soclose(so);
return (error);
}
SOCK_LOCK(so);
while ((so->so_state & SS_ISCONNECTING) && so->so_error == 0) {
error = msleep(&so->so_timeo, SOCK_MTX(so), PSOCK | PCATCH,
"icl_connect", 0);
if (error) {
if (error == EINTR || error == ERESTART)
interrupted = 1;
break;
}
}
if (error == 0) {
error = so->so_error;
so->so_error = 0;
}
SOCK_UNLOCK(so);
if (error != 0) {
soclose(so);
return (error);
}
error = icl_conn_handoff_sock(ic, so);
if (error != 0)
soclose(so);
return (error);
}
int
icl_conn_connect(struct icl_conn *ic, bool rdma, int domain, int socktype,
int protocol, struct sockaddr *from_sa, struct sockaddr *to_sa)
{
if (rdma) {
#ifdef ICL_RDMA
return (icl_conn_connect_rdma(ic, domain, socktype, protocol, from_sa, to_sa));
#else
ICL_DEBUG("RDMA not supported");
return (EOPNOTSUPP);
#endif
}
return (icl_conn_connect_tcp(ic, domain, socktype, protocol, from_sa, to_sa));
}
struct icl_listen *
icl_listen_new(void (*accept_cb)(struct socket *))
{
struct icl_listen *il;
il = malloc(sizeof(*il), M_ICL_PROXY, M_ZERO | M_WAITOK);
TAILQ_INIT(&il->il_sockets);
sx_init(&il->il_lock, "icl_listen");
il->il_accept = accept_cb;
return (il);
}
void
icl_listen_free(struct icl_listen *il)
{
struct icl_listen_sock *ils;
sx_xlock(&il->il_lock);
while (!TAILQ_EMPTY(&il->il_sockets)) {
ils = TAILQ_FIRST(&il->il_sockets);
while (ils->ils_running) {
ICL_DEBUG("waiting for accept thread to terminate");
sx_xunlock(&il->il_lock);
ils->ils_disconnecting = true;
wakeup(&ils->ils_socket->so_timeo);
pause("icl_unlisten", 1 * hz);
sx_xlock(&il->il_lock);
}
TAILQ_REMOVE(&il->il_sockets, ils, ils_next);
soclose(ils->ils_socket);
free(ils, M_ICL_PROXY);
}
sx_xunlock(&il->il_lock);
free(il, M_ICL_PROXY);
}
/*
* XXX: Doing accept in a separate thread in each socket might not be the best way
* to do stuff, but it's pretty clean and debuggable - and you probably won't
* have hundreds of listening sockets anyway.
*/
static void
icl_accept_thread(void *arg)
{
struct icl_listen_sock *ils;
struct socket *head, *so;
struct sockaddr *sa;
int error;
ils = arg;
head = ils->ils_socket;
ils->ils_running = true;
for (;;) {
ACCEPT_LOCK();
while (TAILQ_EMPTY(&head->so_comp) && head->so_error == 0 && ils->ils_disconnecting == false) {
if (head->so_rcv.sb_state & SBS_CANTRCVMORE) {
head->so_error = ECONNABORTED;
break;
}
error = msleep(&head->so_timeo, &accept_mtx, PSOCK | PCATCH,
"accept", 0);
if (error) {
ACCEPT_UNLOCK();
ICL_WARN("msleep failed with error %d", error);
continue;
}
if (ils->ils_disconnecting) {
ACCEPT_UNLOCK();
ICL_DEBUG("terminating");
ils->ils_running = false;
kthread_exit();
return;
}
}
if (head->so_error) {
error = head->so_error;
head->so_error = 0;
ACCEPT_UNLOCK();
ICL_WARN("socket error %d", error);
continue;
}
so = TAILQ_FIRST(&head->so_comp);
KASSERT(so != NULL, ("NULL so"));
KASSERT(!(so->so_qstate & SQ_INCOMP), ("accept1: so SQ_INCOMP"));
KASSERT(so->so_qstate & SQ_COMP, ("accept1: so not SQ_COMP"));
/*
* Before changing the flags on the socket, we have to bump the
* reference count. Otherwise, if the protocol calls sofree(),
* the socket will be released due to a zero refcount.
*/
SOCK_LOCK(so); /* soref() and so_state update */
soref(so); /* file descriptor reference */
TAILQ_REMOVE(&head->so_comp, so, so_list);
head->so_qlen--;
so->so_state |= (head->so_state & SS_NBIO);
so->so_qstate &= ~SQ_COMP;
so->so_head = NULL;
SOCK_UNLOCK(so);
ACCEPT_UNLOCK();
sa = NULL;
error = soaccept(so, &sa);
if (error != 0) {
ICL_WARN("soaccept error %d", error);
if (sa != NULL)
free(sa, M_SONAME);
soclose(so);
}
(ils->ils_listen->il_accept)(so);
}
}
static int
icl_listen_add_tcp(struct icl_listen *il, int domain, int socktype, int protocol,
struct sockaddr *sa)
{
struct icl_listen_sock *ils;
struct socket *so;
struct sockopt sopt;
int error, one = 1;
error = socreate(domain, &so, socktype, protocol,
curthread->td_ucred, curthread);
if (error != 0) {
ICL_WARN("socreate failed with error %d", error);
return (error);
}
sopt.sopt_dir = SOPT_SET;
sopt.sopt_level = SOL_SOCKET;
sopt.sopt_name = SO_REUSEADDR;
sopt.sopt_val = &one;
sopt.sopt_valsize = sizeof(one);
sopt.sopt_td = NULL;
error = sosetopt(so, &sopt);
if (error != 0) {
ICL_WARN("failed to set SO_REUSEADDR with error %d", error);
soclose(so);
return (error);
}
error = sobind(so, sa, curthread);
if (error != 0) {
ICL_WARN("sobind failed with error %d", error);
soclose(so);
return (error);
}
error = solisten(so, -1, curthread);
if (error != 0) {
ICL_WARN("solisten failed with error %d", error);
soclose(so);
return (error);
}
ils = malloc(sizeof(*ils), M_ICL_PROXY, M_ZERO | M_WAITOK);
ils->ils_listen = il;
ils->ils_socket = so;
error = kthread_add(icl_accept_thread, ils, NULL, NULL, 0, 0, "iclacc");
if (error != 0) {
ICL_WARN("kthread_add failed with error %d", error);
soclose(so);
free(ils, M_ICL_PROXY);
return (error);
}
sx_xlock(&il->il_lock);
TAILQ_INSERT_TAIL(&il->il_sockets, ils, ils_next);
sx_xunlock(&il->il_lock);
return (0);
}
int
icl_listen_add(struct icl_listen *il, bool rdma, int domain, int socktype, int protocol,
struct sockaddr *sa)
{
if (rdma) {
#ifndef ICL_RDMA
ICL_DEBUG("RDMA not supported");
return (EOPNOTSUPP);
#else
return (icl_listen_add_rdma(il, domain, socktype, protocol, sa));
#endif
}
return (icl_listen_add_tcp(il, domain, socktype, protocol, sa));
}
int
icl_listen_remove(struct icl_listen *il, struct sockaddr *sa)
{
/*
* XXX
*/
return (EOPNOTSUPP);
}
#endif /* ICL_KERNEL_PROXY */

2109
sys/dev/iscsi/iscsi.c Normal file

File diff suppressed because it is too large Load Diff

135
sys/dev/iscsi/iscsi.h Normal file
View File

@ -0,0 +1,135 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef ISCSI_H
#define ISCSI_H
struct iscsi_softc;
struct icl_conn;
#define ISCSI_NAME_LEN 224 /* 223 bytes, by RFC 3720, + '\0' */
#define ISCSI_ADDR_LEN 47 /* INET6_ADDRSTRLEN + '\0' */
#define ISCSI_SECRET_LEN 17 /* 16 + '\0' */
struct iscsi_outstanding {
TAILQ_ENTRY(iscsi_outstanding) io_next;
union ccb *io_ccb;
size_t io_received;
uint32_t io_initiator_task_tag;
uint32_t io_datasn;
};
struct iscsi_session {
TAILQ_ENTRY(iscsi_session) is_next;
struct icl_conn *is_conn;
struct mtx is_lock;
uint32_t is_statsn;
uint32_t is_cmdsn;
uint32_t is_expcmdsn;
uint32_t is_maxcmdsn;
uint32_t is_initiator_task_tag;
int is_header_digest;
int is_data_digest;
int is_initial_r2t;
size_t is_max_burst_length;
size_t is_first_burst_length;
uint8_t is_isid[6];
bool is_immediate_data;
size_t is_max_data_segment_length;
char is_target_alias[ISCSI_ALIAS_LEN];
TAILQ_HEAD(, iscsi_outstanding) is_outstanding;
TAILQ_HEAD(, icl_pdu) is_postponed;
struct callout is_callout;
unsigned int is_timeout;
/*
* XXX: This could be rewritten using a single variable,
* but somehow it results in uglier code.
*/
/*
* We're waiting for iscsid(8); after iscsid_timeout
* expires, kernel will wake up an iscsid(8) to handle
* the session.
*/
bool is_waiting_for_iscsid;
/*
* Some iscsid(8) instance is handling the session;
* after login_timeout expires, kernel will wake up
* another iscsid(8) to handle the session.
*/
bool is_login_phase;
/*
* We're in the process of removing the iSCSI session.
*/
bool is_terminating;
/*
* We're waiting for the maintenance thread to do some
* reconnection tasks.
*/
bool is_reconnecting;
bool is_connected;
struct cam_devq *is_devq;
struct cam_sim *is_sim;
struct cam_path *is_path;
struct cv is_maintenance_cv;
struct iscsi_softc *is_softc;
unsigned int is_id;
struct iscsi_session_conf is_conf;
bool is_simq_frozen;
char is_reason[ISCSI_REASON_LEN];
#ifdef ICL_KERNEL_PROXY
struct cv is_login_cv;;
struct icl_pdu *is_login_pdu;
#endif
};
struct iscsi_softc {
device_t sc_dev;
struct sx sc_lock;
struct cdev *sc_cdev;
TAILQ_HEAD(, iscsi_session) sc_sessions;
struct cv sc_cv;
unsigned int sc_last_session_id;
eventhandler_tag sc_shutdown_eh;
};
#endif /* !ISCSI_H */

201
sys/dev/iscsi/iscsi_ioctl.h Normal file
View File

@ -0,0 +1,201 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef ISCSI_IOCTL_H
#define ISCSI_IOCTL_H
#ifdef ICL_KERNEL_PROXY
#include <sys/socket.h>
#endif
#define ISCSI_PATH "/dev/iscsi"
#define ISCSI_MAX_DATA_SEGMENT_LENGTH (128 * 1024)
#define ISCSI_NAME_LEN 224 /* 223 bytes, by RFC 3720, + '\0' */
#define ISCSI_ADDR_LEN 47 /* INET6_ADDRSTRLEN + '\0' */
#define ISCSI_ALIAS_LEN 256 /* XXX: Where did it come from? */
#define ISCSI_SECRET_LEN 17 /* 16 + '\0' */
#define ISCSI_REASON_LEN 32
#define ISCSI_DIGEST_NONE 0
#define ISCSI_DIGEST_CRC32C 1
/*
* Session configuration, set when adding the session.
*/
struct iscsi_session_conf {
char isc_initiator[ISCSI_NAME_LEN];
char isc_initiator_addr[ISCSI_ADDR_LEN];
char isc_initiator_alias[ISCSI_ALIAS_LEN];
char isc_target[ISCSI_NAME_LEN];
char isc_target_addr[ISCSI_ADDR_LEN];
char isc_user[ISCSI_NAME_LEN];
char isc_secret[ISCSI_SECRET_LEN];
char isc_mutual_user[ISCSI_NAME_LEN];
char isc_mutual_secret[ISCSI_SECRET_LEN];
int isc_discovery;
int isc_header_digest;
int isc_data_digest;
int isc_iser;
int isc_spare[4];
};
/*
* Session state, negotiated by iscsid(8) and queried by iscsictl(8).
*/
struct iscsi_session_state {
struct iscsi_session_conf iss_conf;
unsigned int iss_id;
char iss_target_alias[ISCSI_ALIAS_LEN];
int iss_header_digest;
int iss_data_digest;
int iss_max_data_segment_length;
int iss_immediate_data;
int iss_connected;
char iss_reason[ISCSI_REASON_LEN];
int iss_spare[4];
};
/*
* For use with iscsid(8).
*/
struct iscsi_daemon_request {
unsigned int idr_session_id;
struct iscsi_session_conf idr_conf;
int idr_spare[4];
};
struct iscsi_daemon_handoff {
unsigned int idh_session_id;
int idh_socket;
char idh_target_alias[ISCSI_ALIAS_LEN];
uint8_t idh_isid[6];
uint32_t idh_statsn;
int idh_header_digest;
int idh_data_digest;
int idh_initial_r2t;
int idh_immediate_data;
size_t idh_max_data_segment_length;
size_t idh_max_burst_length;
size_t idh_first_burst_length;
};
struct iscsi_daemon_fail {
unsigned int idf_session_id;
char idf_reason[ISCSI_REASON_LEN];
};
#define ISCSIDWAIT _IOR('I', 0x01, struct iscsi_daemon_request)
#define ISCSIDHANDOFF _IOW('I', 0x02, struct iscsi_daemon_handoff)
#define ISCSIDFAIL _IOW('I', 0x03, struct iscsi_daemon_fail)
#ifdef ICL_KERNEL_PROXY
/*
* When ICL_KERNEL_PROXY is not defined, the iscsid(8) is responsible
* for creating the socket, connecting, performing Login Phase using
* socked in the usual userspace way, and then passing the socket file
* descriptor to the kernel part using ISCSIDHANDOFF.
*
* When ICL_KERNEL_PROXY is defined, the iscsid(8) creates the session
* using ISCSICONNECT, performs Login Phase using ISCSISEND/ISCSIRECEIVE
* instead of read(2)/write(2), and then calls ISCSIDHANDOFF with
* idh_socket set to 0.
*
* The purpose of ICL_KERNEL_PROXY is to workaround the fact that,
* at this time, it's not possible to do iWARP (RDMA) in userspace.
*/
struct iscsi_daemon_connect {
int idc_session_id;
int idc_iser;
int idc_domain;
int idc_socktype;
int idc_protocol;
struct sockaddr *idc_from_addr;
socklen_t idc_from_addrlen;
struct sockaddr *idc_to_addr;
socklen_t idc_to_addrlen;
};
struct iscsi_daemon_send {
int ids_session_id;
void *ids_bhs;
size_t ids_spare;
void *ids_spare2;
size_t ids_data_segment_len;
void *ids_data_segment;
};
struct iscsi_daemon_receive {
int idr_session_id;
void *idr_bhs;
size_t idr_spare;
void *idr_spare2;
size_t idr_data_segment_len;
void *idr_data_segment;
};
struct iscsi_daemon_close {
int idc_session_id;
};
#define ISCSIDCONNECT _IOWR('I', 0x04, struct iscsi_daemon_connect)
#define ISCSIDSEND _IOWR('I', 0x05, struct iscsi_daemon_send)
#define ISCSIDRECEIVE _IOWR('I', 0x06, struct iscsi_daemon_receive)
#define ISCSIDCLOSE _IOWR('I', 0x07, struct iscsi_daemon_close)
#endif /* ICL_KERNEL_PROXY */
/*
* For use with iscsictl(8).
*/
struct iscsi_session_add {
struct iscsi_session_conf isa_conf;
};
struct iscsi_session_remove {
unsigned int isr_session_id;
struct iscsi_session_conf isr_conf;
};
struct iscsi_session_list {
unsigned int isl_nentries;
struct iscsi_session_state *isl_pstates;
};
#define ISCSISADD _IOW('I', 0x11, struct iscsi_session_add)
#define ISCSISREMOVE _IOW('I', 0x12, struct iscsi_session_remove)
#define ISCSISLIST _IOWR('I', 0x13, struct iscsi_session_list)
#endif /* !ISCSI_IOCTL_H */

439
sys/dev/iscsi/iscsi_proto.h Normal file
View File

@ -0,0 +1,439 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef ISCSI_PROTO_H
#define ISCSI_PROTO_H
#ifndef CTASSERT
#define CTASSERT(x) _CTASSERT(x, __LINE__)
#define _CTASSERT(x, y) __CTASSERT(x, y)
#define __CTASSERT(x, y) typedef char __assert_ ## y [(x) ? 1 : -1]
#endif
#define ISCSI_BHS_SIZE 48
#define ISCSI_HEADER_DIGEST_SIZE 4
#define ISCSI_DATA_DIGEST_SIZE 4
#define ISCSI_BHS_OPCODE_IMMEDIATE 0x40
#define ISCSI_BHS_OPCODE_NOP_OUT 0x00
#define ISCSI_BHS_OPCODE_SCSI_COMMAND 0x01
#define ISCSI_BHS_OPCODE_TASK_REQUEST 0x02
#define ISCSI_BHS_OPCODE_LOGIN_REQUEST 0x03
#define ISCSI_BHS_OPCODE_TEXT_REQUEST 0x04
#define ISCSI_BHS_OPCODE_SCSI_DATA_OUT 0x05
#define ISCSI_BHS_OPCODE_LOGOUT_REQUEST 0x06
#define ISCSI_BHS_OPCODE_NOP_IN 0x20
#define ISCSI_BHS_OPCODE_SCSI_RESPONSE 0x21
#define ISCSI_BHS_OPCODE_TASK_RESPONSE 0x22
#define ISCSI_BHS_OPCODE_LOGIN_RESPONSE 0x23
#define ISCSI_BHS_OPCODE_TEXT_RESPONSE 0x24
#define ISCSI_BHS_OPCODE_SCSI_DATA_IN 0x25
#define ISCSI_BHS_OPCODE_LOGOUT_RESPONSE 0x26
#define ISCSI_BHS_OPCODE_R2T 0x31
#define ISCSI_BHS_OPCODE_ASYNC_MESSAGE 0x32
#define ISCSI_BHS_OPCODE_REJECT 0x3f
struct iscsi_bhs {
uint8_t bhs_opcode;
uint8_t bhs_opcode_specific1[3];
uint8_t bhs_total_ahs_len;
uint8_t bhs_data_segment_len[3];
uint64_t bhs_lun;
uint8_t bhs_inititator_task_tag[4];
uint8_t bhs_opcode_specific4[28];
};
CTASSERT(sizeof(struct iscsi_bhs) == ISCSI_BHS_SIZE);
#define BHSSC_FLAGS_F 0x80
#define BHSSC_FLAGS_R 0x40
#define BHSSC_FLAGS_W 0x20
#define BHSSC_FLAGS_ATTR 0x07
#define BHSSC_FLAGS_ATTR_UNTAGGED 0
#define BHSSC_FLAGS_ATTR_SIMPLE 1
#define BHSSC_FLAGS_ATTR_ORDERED 2
#define BHSSC_FLAGS_ATTR_HOQ 3
#define BHSSC_FLAGS_ATTR_ACA 4
struct iscsi_bhs_scsi_command {
uint8_t bhssc_opcode;
uint8_t bhssc_flags;
uint8_t bhssc_reserved[2];
uint8_t bhssc_total_ahs_len;
uint8_t bhssc_data_segment_len[3];
uint64_t bhssc_lun;
uint32_t bhssc_initiator_task_tag;
uint32_t bhssc_expected_data_transfer_length;
uint32_t bhssc_cmdsn;
uint32_t bhssc_expstatsn;
uint8_t bhssc_cdb[16];
};
CTASSERT(sizeof(struct iscsi_bhs_scsi_command) == ISCSI_BHS_SIZE);
#define BHSSR_FLAGS_RESIDUAL_UNDERFLOW 0x02
#define BHSSR_FLAGS_RESIDUAL_OVERFLOW 0x04
#define BHSSR_RESPONSE_COMMAND_COMPLETED 0x00
struct iscsi_bhs_scsi_response {
uint8_t bhssr_opcode;
uint8_t bhssr_flags;
uint8_t bhssr_response;
uint8_t bhssr_status;
uint8_t bhssr_total_ahs_len;
uint8_t bhssr_data_segment_len[3];
uint64_t bhssr_reserved;
uint32_t bhssr_initiator_task_tag;
uint32_t bhssr_snack_tag;
uint32_t bhssr_statsn;
uint32_t bhssr_expcmdsn;
uint32_t bhssr_maxcmdsn;
uint32_t bhssr_expdatasn;
uint32_t bhssr_bidirectional_read_residual_count;
uint32_t bhssr_residual_count;
};
CTASSERT(sizeof(struct iscsi_bhs_scsi_response) == ISCSI_BHS_SIZE);
#define BHSTMR_FUNCTION_ABORT_TASK 1
#define BHSTMR_FUNCTION_ABORT_TASK_SET 2
#define BHSTMR_FUNCTION_CLEAR_ACA 3
#define BHSTMR_FUNCTION_CLEAR_TASK_SET 4
#define BHSTMR_FUNCTION_LOGICAL_UNIT_RESET 5
#define BHSTMR_FUNCTION_TARGET_WARM_RESET 6
#define BHSTMR_FUNCTION_TARGET_COLD_RESET 7
#define BHSTMR_FUNCTION_TASK_REASSIGN 8
struct iscsi_bhs_task_management_request {
uint8_t bhstmr_opcode;
uint8_t bhstmr_function;
uint8_t bhstmr_reserved[2];
uint8_t bhstmr_total_ahs_len;
uint8_t bhstmr_data_segment_len[3];
uint64_t bhstmr_lun;
uint32_t bhstmr_initiator_task_tag;
uint32_t bhstmr_referenced_task_tag;
uint32_t bhstmr_cmdsn;
uint32_t bhstmr_expstatsn;
uint32_t bhstmr_refcmdsn;
uint32_t bhstmr_expdatasn;
uint64_t bhstmr_reserved2;
};
CTASSERT(sizeof(struct iscsi_bhs_task_management_request) == ISCSI_BHS_SIZE);
#define BHSTMR_RESPONSE_FUNCTION_COMPLETE 0
#define BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED 5
struct iscsi_bhs_task_management_response {
uint8_t bhstmr_opcode;
uint8_t bhstmr_flags;
uint8_t bhstmr_response;
uint8_t bhstmr_reserved;
uint8_t bhstmr_total_ahs_len;
uint8_t bhstmr_data_segment_len[3];
uint64_t bhstmr_reserved2;
uint32_t bhstmr_initiator_task_tag;
uint32_t bhstmr_reserved3;
uint32_t bhstmr_statsn;
uint32_t bhstmr_expcmdsn;
uint32_t bhstmr_maxcmdsn;
uint8_t bhstmr_reserved4[12];
};
CTASSERT(sizeof(struct iscsi_bhs_task_management_response) == ISCSI_BHS_SIZE);
#define BHSLR_FLAGS_TRANSIT 0x80
#define BHSLR_FLAGS_CONTINUE 0x40
#define BHSLR_STAGE_SECURITY_NEGOTIATION 0
#define BHSLR_STAGE_OPERATIONAL_NEGOTIATION 1
#define BHSLR_STAGE_FULL_FEATURE_PHASE 3 /* Yes, 3. */
struct iscsi_bhs_login_request {
uint8_t bhslr_opcode;
uint8_t bhslr_flags;
uint8_t bhslr_version_max;
uint8_t bhslr_version_min;
uint8_t bhslr_total_ahs_len;
uint8_t bhslr_data_segment_len[3];
uint8_t bhslr_isid[6];
uint16_t bhslr_tsih;
uint32_t bhslr_initiator_task_tag;
uint16_t bhslr_cid;
uint16_t bhslr_reserved;
uint32_t bhslr_cmdsn;
uint32_t bhslr_expstatsn;
uint8_t bhslr_reserved2[16];
};
CTASSERT(sizeof(struct iscsi_bhs_login_request) == ISCSI_BHS_SIZE);
struct iscsi_bhs_login_response {
uint8_t bhslr_opcode;
uint8_t bhslr_flags;
uint8_t bhslr_version_max;
uint8_t bhslr_version_active;
uint8_t bhslr_total_ahs_len;
uint8_t bhslr_data_segment_len[3];
uint8_t bhslr_isid[6];
uint16_t bhslr_tsih;
uint32_t bhslr_initiator_task_tag;
uint32_t bhslr_reserved;
uint32_t bhslr_statsn;
uint32_t bhslr_expcmdsn;
uint32_t bhslr_maxcmdsn;
uint8_t bhslr_status_class;
uint8_t bhslr_status_detail;
uint16_t bhslr_reserved2;
uint8_t bhslr_reserved3[8];
};
CTASSERT(sizeof(struct iscsi_bhs_login_response) == ISCSI_BHS_SIZE);
#define BHSTR_FLAGS_FINAL 0x80
#define BHSTR_FLAGS_CONTINUE 0x40
struct iscsi_bhs_text_request {
uint8_t bhstr_opcode;
uint8_t bhstr_flags;
uint16_t bhstr_reserved;
uint8_t bhstr_total_ahs_len;
uint8_t bhstr_data_segment_len[3];
uint64_t bhstr_lun;
uint32_t bhstr_initiator_task_tag;
uint32_t bhstr_target_transfer_tag;
uint32_t bhstr_cmdsn;
uint32_t bhstr_expstatsn;
uint8_t bhstr_reserved2[16];
};
CTASSERT(sizeof(struct iscsi_bhs_text_request) == ISCSI_BHS_SIZE);
struct iscsi_bhs_text_response {
uint8_t bhstr_opcode;
uint8_t bhstr_flags;
uint16_t bhstr_reserved;
uint8_t bhstr_total_ahs_len;
uint8_t bhstr_data_segment_len[3];
uint64_t bhstr_lun;
uint32_t bhstr_initiator_task_tag;
uint32_t bhstr_target_transfer_tag;
uint32_t bhstr_statsn;
uint32_t bhstr_expcmdsn;
uint32_t bhstr_maxcmdsn;
uint8_t bhstr_reserved2[12];
};
CTASSERT(sizeof(struct iscsi_bhs_text_response) == ISCSI_BHS_SIZE);
#define BHSDO_FLAGS_F 0x80
struct iscsi_bhs_data_out {
uint8_t bhsdo_opcode;
uint8_t bhsdo_flags;
uint8_t bhsdo_reserved[2];
uint8_t bhsdo_total_ahs_len;
uint8_t bhsdo_data_segment_len[3];
uint64_t bhsdo_lun;
uint32_t bhsdo_initiator_task_tag;
uint32_t bhsdo_target_transfer_tag;
uint32_t bhsdo_reserved2;
uint32_t bhsdo_expstatsn;
uint32_t bhsdo_reserved3;
uint32_t bhsdo_datasn;
uint32_t bhsdo_buffer_offset;
uint32_t bhsdo_reserved4;
};
CTASSERT(sizeof(struct iscsi_bhs_data_out) == ISCSI_BHS_SIZE);
#define BHSDI_FLAGS_F 0x80
#define BHSDI_FLAGS_A 0x40
#define BHSDI_FLAGS_O 0x04
#define BHSDI_FLAGS_U 0x02
#define BHSDI_FLAGS_S 0x01
struct iscsi_bhs_data_in {
uint8_t bhsdi_opcode;
uint8_t bhsdi_flags;
uint8_t bhsdi_reserved;
uint8_t bhsdi_status;
uint8_t bhsdi_total_ahs_len;
uint8_t bhsdi_data_segment_len[3];
uint64_t bhsdi_lun;
uint32_t bhsdi_initiator_task_tag;
uint32_t bhsdi_target_transfer_tag;
uint32_t bhsdi_statsn;
uint32_t bhsdi_expcmdsn;
uint32_t bhsdi_maxcmdsn;
uint32_t bhsdi_datasn;
uint32_t bhsdi_buffer_offset;
uint32_t bhsdi_residual_count;
};
CTASSERT(sizeof(struct iscsi_bhs_data_in) == ISCSI_BHS_SIZE);
struct iscsi_bhs_r2t {
uint8_t bhsr2t_opcode;
uint8_t bhsr2t_flags;
uint16_t bhsr2t_reserved;
uint8_t bhsr2t_total_ahs_len;
uint8_t bhsr2t_data_segment_len[3];
uint64_t bhsr2t_lun;
uint32_t bhsr2t_initiator_task_tag;
uint32_t bhsr2t_target_transfer_tag;
uint32_t bhsr2t_statsn;
uint32_t bhsr2t_expcmdsn;
uint32_t bhsr2t_maxcmdsn;
uint32_t bhsr2t_r2tsn;
uint32_t bhsr2t_buffer_offset;
uint32_t bhsr2t_desired_data_transfer_length;
};
CTASSERT(sizeof(struct iscsi_bhs_r2t) == ISCSI_BHS_SIZE);
struct iscsi_bhs_nop_out {
uint8_t bhsno_opcode;
uint8_t bhsno_flags;
uint16_t bhsno_reserved;
uint8_t bhsno_total_ahs_len;
uint8_t bhsno_data_segment_len[3];
uint64_t bhsno_lun;
uint32_t bhsno_initiator_task_tag;
uint32_t bhsno_target_transfer_tag;
uint32_t bhsno_cmdsn;
uint32_t bhsno_expstatsn;
uint8_t bhsno_reserved2[16];
};
CTASSERT(sizeof(struct iscsi_bhs_nop_out) == ISCSI_BHS_SIZE);
struct iscsi_bhs_nop_in {
uint8_t bhsni_opcode;
uint8_t bhsni_flags;
uint16_t bhsni_reserved;
uint8_t bhsni_total_ahs_len;
uint8_t bhsni_data_segment_len[3];
uint64_t bhsni_lun;
uint32_t bhsni_initiator_task_tag;
uint32_t bhsni_target_transfer_tag;
uint32_t bhsni_statsn;
uint32_t bhsni_expcmdsn;
uint32_t bhsni_maxcmdsn;
uint8_t bhsno_reserved2[12];
};
CTASSERT(sizeof(struct iscsi_bhs_nop_in) == ISCSI_BHS_SIZE);
#define BHSLR_REASON_CLOSE_SESSION 0
#define BHSLR_REASON_CLOSE_CONNECTION 1
#define BHSLR_REASON_REMOVE_FOR_RECOVERY 2
struct iscsi_bhs_logout_request {
uint8_t bhslr_opcode;
uint8_t bhslr_reason;
uint16_t bhslr_reserved;
uint8_t bhslr_total_ahs_len;
uint8_t bhslr_data_segment_len[3];
uint64_t bhslr_reserved2;
uint32_t bhslr_initiator_task_tag;
uint16_t bhslr_cid;
uint16_t bhslr_reserved3;
uint32_t bhslr_cmdsn;
uint32_t bhslr_expstatsn;
uint8_t bhslr_reserved4[16];
};
CTASSERT(sizeof(struct iscsi_bhs_logout_request) == ISCSI_BHS_SIZE);
#define BHSLR_RESPONSE_CLOSED_SUCCESSFULLY 0
#define BHSLR_RESPONSE_RECOVERY_NOT_SUPPORTED 2
struct iscsi_bhs_logout_response {
uint8_t bhslr_opcode;
uint8_t bhslr_flags;
uint8_t bhslr_response;
uint8_t bhslr_reserved;
uint8_t bhslr_total_ahs_len;
uint8_t bhslr_data_segment_len[3];
uint64_t bhslr_reserved2;
uint32_t bhslr_initiator_task_tag;
uint32_t bhslr_reserved3;
uint32_t bhslr_statsn;
uint32_t bhslr_expcmdsn;
uint32_t bhslr_maxcmdsn;
uint32_t bhslr_reserved4;
uint16_t bhslr_time2wait;
uint16_t bhslr_time2retain;
uint32_t bhslr_reserved5;
};
CTASSERT(sizeof(struct iscsi_bhs_logout_response) == ISCSI_BHS_SIZE);
#define BHSAM_EVENT_TARGET_REQUESTS_LOGOUT 1
#define BHSAM_EVENT_TARGET_TERMINATES_CONNECTION 2
#define BHSAM_EVENT_TARGET_TERMINATES_SESSION 3
struct iscsi_bhs_asynchronous_message {
uint8_t bhsam_opcode;
uint8_t bhsam_flags;
uint16_t bhsam_reserved;
uint8_t bhsam_total_ahs_len;
uint8_t bhsam_data_segment_len[3];
uint64_t bhsam_lun;
uint32_t bhsam_0xffffffff;
uint32_t bhsam_reserved2;
uint32_t bhsam_statsn;
uint32_t bhsam_expcmdsn;
uint32_t bhsam_maxcmdsn;
uint8_t bhsam_async_event;
uint8_t bhsam_async_vcode;
uint16_t bhsam_parameter1;
uint16_t bhsam_parameter2;
uint16_t bhsam_parameter3;
uint32_t bhsam_reserved3;
};
CTASSERT(sizeof(struct iscsi_bhs_asynchronous_message) == ISCSI_BHS_SIZE);
#define BHSSR_REASON_DATA_DIGEST_ERROR 0x02
#define BHSSR_PROTOCOL_ERROR 0x04
#define BHSSR_COMMAND_NOT_SUPPORTED 0x05
#define BHSSR_INVALID_PDU_FIELD 0x09
struct iscsi_bhs_reject {
uint8_t bhsr_opcode;
uint8_t bhsr_flags;
uint8_t bhsr_reason;
uint8_t bhsr_reserved;
uint8_t bhsr_total_ahs_len;
uint8_t bhsr_data_segment_len[3];
uint64_t bhsr_reserved2;
uint32_t bhsr_0xffffffff;
uint32_t bhsr_reserved3;
uint32_t bhsr_statsn;
uint32_t bhsr_expcmdsn;
uint32_t bhsr_maxcmdsn;
uint32_t bhsr_datasn_r2tsn;
uint32_t bhsr_reserved4;
uint32_t bhsr_reserved5;
};
CTASSERT(sizeof(struct iscsi_bhs_reject) == ISCSI_BHS_SIZE);
#endif /* !ISCSI_PROTO_H */

View File

@ -858,7 +858,7 @@ iscsi_modevent(module_t mod, int what, void *arg)
}
moduledata_t iscsi_mod = {
"iscsi",
"iscsi_initiator",
(modeventhand_t) iscsi_modevent,
0
};
@ -878,5 +878,5 @@ iscsi_rootconf(void)
SYSINIT(cpu_rootconf1, SI_SUB_ROOT_CONF, SI_ORDER_FIRST, iscsi_rootconf, NULL)
#endif
DECLARE_MODULE(iscsi, iscsi_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(iscsi, cam, 1, 1, 1);
DECLARE_MODULE(iscsi_initiator, iscsi_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(iscsi_initiator, cam, 1, 1, 1);

View File

@ -160,6 +160,7 @@ SUBDIR= \
${_ipw} \
${_ipwfw} \
${_isci} \
iscsi \
iscsi_initiator \
isp \
${_ispfw} \

View File

@ -12,6 +12,7 @@ SRCS+= ctl_cmd_table.c
SRCS+= ctl_frontend.c
SRCS+= ctl_frontend_cam_sim.c
SRCS+= ctl_frontend_internal.c
SRCS+= ctl_frontend_iscsi.c
SRCS+= ctl_mem_pool.c
SRCS+= ctl_scsi_all.c
SRCS+= ctl_error.c
@ -23,4 +24,6 @@ SRCS+= vnode_if.h
SRCS+= opt_cam.h
SRCS+= opt_kdtrace.h
#CFLAGS+=-DICL_KERNEL_PROXY
.include <bsd.kmod.mk>

View File

@ -1,5 +1,25 @@
# $FreeBSD$
SUBDIR= initiator
.PATH: ${.CURDIR}/../../dev/iscsi/
KMOD= iscsi
.include <bsd.subdir.mk>
SRCS= iscsi.c
.if defined(ICL_RDMA)
SRCS+= icl_rdma.c
.else
SRCS+= icl.c
.endif
SRCS+= icl_proxy.c
SRCS+= opt_cam.h
SRCS+= bus_if.h
SRCS+= device_if.h
# Those below are required for RDMA.
SRCS+= vnode_if.h
SRCS+= opt_inet.h
SRCS+= opt_inet6.h
CFLAGS+= -I${.CURDIR}/../../ofed/include
#CFLAGS+=-DICL_KERNEL_PROXY
.include <bsd.kmod.mk>

View File

@ -0,0 +1,35 @@
auth-group meh {
chap user secretsecret
}
portal-group meh {
listen 0.0.0.0
discovery-auth-group no-authentication
}
target iqn.2012-06.com.example:1 {
auth-group no-authentication
portal-group meh
lun 0 {
path /var/tmp/example_t1l0
size 4G
}
lun 1 {
path /var/tmp/example_t1l1
size 4G
}
}
target iqn.2012-06.com.example:2 {
auth-group meh
portal-group meh
lun 0 {
path /var/tmp/example_t2l0
size 4G
}
lun 1 {
path /var/tmp/example_t2l1
size 4G
}
}

View File

@ -0,0 +1,59 @@
How to prepare initiator virtual machines for iSCSI target testing
------------------------------------------------------------------
1. Install operating systems.
- FreeBSD: Use default settings for everything. Don't install
ports from the system installer, use "portsnap fetch extract"
after installation instead.
- Fedora: Change the environment to "Minimal install".
- Solaris: Use defaults.
2. Install required software.
- FreeBSD: install from ports, with 'make install BATCH=1':
benchmarks/bonnie++
benchmarks/iozone
benchmarks/postmark
databases/postgresql92-server
databases/postgresql92-contrib
- Fedora:
yum install btrfs-progs bonnie++ postgresql-server postgresql-contrib iscsi-initiator-utils
chkconfig iscsid on
chkconfig iscsi on
After that, install iozone and postmark from source; they are not
provided by Fedora; download sites:
http://www.iozone.org/src/current/iozone3_397.tar
http://www.gtlib.gatech.edu/pub/debian/pool/main/p/postmark/postmark_1.53.orig.tar.gz
To build iozone, use "make linux". Copy the 'postmark' and 'iozone' binaries
to /usr/local/bin/.
- Solaris:
Install gcc:
pkg install gcc-45
pkg install system/header
After that, install bonnie++, iozone, and postmark from source; download sites:
http://www.coker.com.au/bonnie++/experimental/bonnie++-1.97.tgz
http://www.iozone.org/src/current/iozone3_397.tar
http://www.gtlib.gatech.edu/pub/debian/pool/main/p/postmark/postmark_1.53.orig.tar.gz
To build iozone, use "make Solaris10gcc". Copy the 'bonnie++', 'postmark', and 'iozone'
binaries to /usr/bin/.
Fetch the binary PostgreSQL distribution from the link below and untar to /usr/postgres/:
http://ftp.postgresql.org/pub/binary/v9.2.3/solaris/solaris11/i386/postgresql-9.2.3-S11.i386-32.tar.bz2
3. Run the test script.
./iscsi-test.sh

View File

@ -0,0 +1,794 @@
#!/bin/sh
#
# Copyright (c) 2012 The FreeBSD Foundation
# All rights reserved.
#
# This software was developed by Edward Tomasz Napierala under sponsorship
# from the FreeBSD Foundation.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $FreeBSD$
#
#
# This expects that the iSCSI server being tested is at $TARGETIP and exports
# two targets: $TARGET1 and $TARGET2; the former requiring no authentication,
# and the latter using CHAP with user $USER and secret $SECRET. Discovery
# must be permitted without authentication. Each target must contain exactly
# two LUNs, 4GB each. For example, ctl.conf(5) should look like this:
#
# auth-group meh {
# chap user secretsecret
# }
#
# portal-group meh {
# listen 0.0.0.0
# discovery-auth-group no-authentication
# }
#
# target iqn.2012-06.com.example:1 {
# auth-group no-authentication
# portal-group meh
# lun 0 {
# path /var/tmp/example_t1l0
# size 4G
# }
# lun 1 {
# path /var/tmp/example_t1l1
# size 4G
# }
# }
#
# target iqn.2012-06.com.example:2 {
# auth-group meh
# portal-group meh
# lun 0 {
# path /var/tmp/example_t2l0
# size 4G
# }
# lun 1 {
# path /var/tmp/example_t2l1
# size 4G
# }
# }
#
# Remember to create the backing files (/var/tmp/example_t1l0 etcc)
#
# On the initiator, $MNTDIR will be used for testing.
#
TARGETIP=192.168.56.101
TARGET1=iqn.2012-06.com.example:1
TARGET2=iqn.2012-06.com.example:2
USER=user
SECRET=secretsecret
MNTDIR=/mnt
TMPDIR=/tmp
die() {
echo "$*"
exit 1
}
case `uname` in
FreeBSD)
LUN0=/dev/da0
LUN1=/dev/da1
LUN2=/dev/da2
LUN3=/dev/da3
ZFSPOOL=iscsipool
;;
Linux)
LUN0=/dev/sdb
LUN1=/dev/sdc
LUN2=/dev/sdd
LUN3=/dev/sde
;;
SunOS)
# LUN names are being set later, during attach.
ZFSPOOL=iscsipool
;;
*)
die "unsupported system"
;;
esac
check() {
echo "# $@" > /dev/stderr
$@ || die "$@ failed"
}
banner() {
echo "Will try to attach to $TARGET1 and $TARGET2 on $TARGETIP,"
echo "user $USER, secret $SECRET. Will use mountpoint $MNTDIR, temporary dir $TMPDIR,"
if [ -n "$LUN0" ]; then
echo "scratch disks $LUN0, $LUN1, $LUN2, $LUN3."
else
echo "scratch disks unknown at this stage."
fi
echo
echo "This script is NOT safe to run on multiuser system."
echo
echo "Press ^C to interrupt; will proceed in 5 seconds."
sleep 5
}
test_discovery_freebsd_9() {
kldload iscsi_initiator
check iscontrol -dt $TARGETIP > $TMPDIR/discovered
cat $TMPDIR/discovered
echo "TargetName=$TARGET1" > $TMPDIR/expected
echo "TargetName=$TARGET2" >> $TMPDIR/expected
check cmp $TMPDIR/expected $TMPDIR/discovered
rm -f $TMPDIR/expected $TMPDIR/discovered
}
test_discovery_freebsd() {
/etc/rc.d/iscsid onestart
check iscsictl -Ad $TARGETIP
sleep 1
iscsictl | awk '{ print $1 }' | sort > $TMPDIR/discovered
printf "Target\n$TARGET1\n$TARGET2\n" | sort > $TMPDIR/expected
check cmp $TMPDIR/expected $TMPDIR/discovered
rm -f $TMPDIR/expected $TMPDIR/discovered
check iscsictl -Ra
sleep 1
}
test_discovery_linux() {
cat > /etc/iscsi/iscsid.conf << END
discovery.sendtargets.auth.authmethod = None
node.startup = manual
END
check iscsiadm -m discovery -t sendtargets -p $TARGETIP > $TMPDIR/discovered
cat $TMPDIR/discovered
echo "$TARGETIP:3260,-1 $TARGET1" > $TMPDIR/expected
echo "$TARGETIP:3260,-1 $TARGET2" >> $TMPDIR/expected
check cmp $TMPDIR/expected $TMPDIR/discovered
rm -f $TMPDIR/expected $TMPDIR/discovered
}
test_discovery_solaris() {
check iscsiadm add discovery-address $TARGETIP
check iscsiadm modify discovery --sendtargets enable
check iscsiadm modify discovery --static enable
check iscsiadm list target | awk '/^Target/ { print $2 }' | sort > $TMPDIR/discovered
check iscsiadm remove discovery-address $TARGETIP
cat $TMPDIR/discovered
echo "$TARGET1" > $TMPDIR/expected
echo "$TARGET2" >> $TMPDIR/expected
check cmp $TMPDIR/expected $TMPDIR/discovered
rm -f $TMPDIR/expected $TMPDIR/discovered
}
test_discovery() {
echo "*** discovery test ***"
case `uname` in
FreeBSD)
case `uname -r` in
9*)
test_discovery_freebsd_9
;;
*)
test_discovery_freebsd
;;
esac
;;
Linux)
test_discovery_linux
;;
SunOS)
test_discovery_solaris
;;
*)
die "unsupported system"
;;
esac
}
test_attach_freebsd_9() {
[ ! -e LUN0 ] || die "$LUN0 already exists"
[ ! -e LUN1 ] || die "$LUN1 already exists"
[ ! -e LUN2 ] || die "$LUN2 already exists"
[ ! -e LUN3 ] || die "$LUN3 already exists"
cat > $TMPDIR/iscsi.conf << END
target1 {
TargetName = $TARGET1
TargetAddress = $TARGETIP
}
target2 {
TargetName = $TARGET2
TargetAddress = $TARGETIP
AuthMethod = CHAP
chapIName = $USER
chapSecret = $SECRET
}
END
check iscontrol -c $TMPDIR/iscsi.conf -n target1
check iscontrol -c $TMPDIR/iscsi.conf -n target2
echo "Waiting 10 seconds for attach to complete."
sleep 10
[ -e $LUN0 ] || die "$LUN0 doesn't exist"
[ -e $LUN1 ] || die "$LUN1 doesn't exist"
[ -e $LUN2 ] || die "$LUN2 doesn't exist"
[ -e $LUN3 ] || die "$LUN3 doesn't exist"
rm $TMPDIR/iscsi.conf
}
test_attach_freebsd() {
[ ! -e LUN0 ] || die "$LUN0 already exists"
[ ! -e LUN1 ] || die "$LUN1 already exists"
[ ! -e LUN2 ] || die "$LUN2 already exists"
[ ! -e LUN3 ] || die "$LUN3 already exists"
cat > $TMPDIR/iscsi.conf << END
target1 {
TargetName = $TARGET1
TargetAddress = $TARGETIP
}
target2 {
TargetName = $TARGET2
TargetAddress = $TARGETIP
AuthMethod = CHAP
chapIName = $USER
chapSecret = $SECRET
}
END
check iscsictl -Ac $TMPDIR/iscsi.conf -n target1
check iscsictl -Ac $TMPDIR/iscsi.conf -n target2
echo "Waiting 10 seconds for attach to complete."
sleep 10
[ -e $LUN0 ] || die "$LUN0 doesn't exist"
[ -e $LUN1 ] || die "$LUN1 doesn't exist"
[ -e $LUN2 ] || die "$LUN2 doesn't exist"
[ -e $LUN3 ] || die "$LUN3 doesn't exist"
rm $TMPDIR/iscsi.conf
}
test_attach_linux() {
check iscsiadm --mode node --targetname "$TARGET1" -p "$TARGETIP:3260" --op=update --name node.session.auth.authmethod --value=None
check iscsiadm --mode node --targetname "$TARGET1" -p "$TARGETIP:3260" --login
check iscsiadm --mode node --targetname "$TARGET2" -p "$TARGETIP:3260" --op=update --name node.session.auth.authmethod --value=CHAP
check iscsiadm --mode node --targetname "$TARGET2" -p "$TARGETIP:3260" --op=update --name node.session.auth.username --value="$USER"
check iscsiadm --mode node --targetname "$TARGET2" -p "$TARGETIP:3260" --op=update --name node.session.auth.password --value="$SECRET"
check iscsiadm --mode node --targetname "$TARGET2" -p "$TARGETIP:3260" --login
}
test_attach_solaris() {
# XXX: How to enter the CHAP secret from the script? For now,
# just use the first target, and thus first two LUNs.
#check iscsiadm modify initiator-node --authentication CHAP
#check iscsiadm modify initiator-node --CHAP-name $USER
#check iscsiadm modify initiator-node --CHAP-secret $SECRET
iscsiadm add static-config $TARGET1,$TARGETIP
LUN0=`iscsiadm list target -S | awk '/OS Device Name/ { print $4 }' | sed -n 1p`
LUN1=`iscsiadm list target -S | awk '/OS Device Name/ { print $4 }' | sed -n 2p`
LUN0=`echo ${LUN0}2 | sed 's/rdsk/dsk/'`
LUN1=`echo ${LUN1}2 | sed 's/rdsk/dsk/'`
[ -n "$LUN0" -a -n "LUN1" ] || die "attach failed"
echo "Scratch disks: $LUN0, $LUN1"
}
test_attach() {
echo "*** attach test ***"
case `uname` in
FreeBSD)
case `uname -r` in
9*)
test_attach_freebsd_9
;;
*)
test_attach_freebsd
;;
esac
;;
Linux)
test_attach_linux
;;
SunOS)
test_attach_solaris
;;
*)
die "unsupported system"
;;
esac
}
test_newfs_freebsd_ufs() {
echo "*** UFS filesystem smoke test ***"
check newfs $LUN0
check newfs $LUN1
check newfs $LUN2
check newfs $LUN3
check fsck -t ufs $LUN0
check fsck -t ufs $LUN1
check fsck -t ufs $LUN2
check fsck -t ufs $LUN3
}
test_newfs_freebsd_zfs() {
echo "*** ZFS filesystem smoke test ***"
check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3
check zpool destroy -f $ZFSPOOL
}
test_newfs_linux_ext4() {
echo "*** ext4 filesystem smoke test ***"
yes | check mkfs $LUN0
yes | check mkfs $LUN1
yes | check mkfs $LUN2
yes | check mkfs $LUN3
check fsck -f $LUN0
check fsck -f $LUN1
check fsck -f $LUN2
check fsck -f $LUN3
}
test_newfs_linux_btrfs() {
echo "*** btrfs filesystem smoke test ***"
check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3
}
test_newfs_solaris_ufs() {
echo "*** UFS filesystem smoke test ***"
yes | check newfs $LUN0
yes | check newfs $LUN1
check fsck -F ufs $LUN0
check fsck -F ufs $LUN1
}
test_newfs_solaris_zfs() {
echo "*** ZFS filesystem smoke test ***"
check zpool create -f $ZFSPOOL $LUN0 $LUN1
check zpool destroy -f $ZFSPOOL
}
test_newfs() {
case `uname` in
FreeBSD)
test_newfs_freebsd_ufs
test_newfs_freebsd_zfs
;;
Linux)
test_newfs_linux_ext4
test_newfs_linux_btrfs
;;
SunOS)
test_newfs_solaris_ufs
test_newfs_solaris_zfs
;;
*)
die "unsupported system"
;;
esac
}
test_cp() {
echo "*** basic filesystem test ***"
case `uname` in
FreeBSD)
check newfs $LUN0
check mount -t ufs $LUN0 $MNTDIR
check dd if=/dev/urandom of=$MNTDIR/1 bs=1m count=500
check cp $MNTDIR/1 $MNTDIR/2
check umount $MNTDIR
check fsck -t ufs $LUN0
check mount -t ufs $LUN0 $MNTDIR
check cmp $MNTDIR/1 $MNTDIR/2
check umount $MNTDIR
check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3
check dd if=/dev/urandom of=/$ZFSPOOL/1 bs=1m count=500
check zpool scrub $ZFSPOOL
check cp /$ZFSPOOL/1 /$ZFSPOOL/2
check cmp /$ZFSPOOL/1 /$ZFSPOOL/2
check zpool destroy -f $ZFSPOOL
;;
Linux)
yes | check mkfs $LUN0
check mount $LUN0 $MNTDIR
check dd if=/dev/urandom of=$MNTDIR/1 bs=1M count=500
check cp $MNTDIR/1 $MNTDIR/2
check umount $MNTDIR
check fsck -f $LUN0
check mount $LUN0 $MNTDIR
check cmp $MNTDIR/1 $MNTDIR/2
check umount $MNTDIR
check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3
check mount $LUN0 $MNTDIR
check dd if=/dev/urandom of=$MNTDIR/1 bs=1M count=500
check cp $MNTDIR/1 $MNTDIR/2
check umount $MNTDIR
check mount $LUN0 $MNTDIR
check cmp $MNTDIR/1 $MNTDIR/2
check umount $MNTDIR
;;
SunOS)
yes | check newfs $LUN0
check mount -F ufs $LUN0 $MNTDIR
check dd if=/dev/urandom of=$MNTDIR/1 bs=1024k count=500
check cp $MNTDIR/1 $MNTDIR/2
check umount $MNTDIR
check fsck -yF ufs $LUN0
check mount -F ufs $LUN0 $MNTDIR
check cmp $MNTDIR/1 $MNTDIR/2
check umount $MNTDIR
check zpool create -f $ZFSPOOL $LUN0 $LUN1
check dd if=/dev/urandom of=/$ZFSPOOL/1 bs=1024k count=500
check zpool scrub $ZFSPOOL
check cp /$ZFSPOOL/1 /$ZFSPOOL/2
check cmp /$ZFSPOOL/1 /$ZFSPOOL/2
check zpool destroy -f $ZFSPOOL
;;
*)
die "unsupported system"
;;
esac
}
test_bonnie() {
echo "*** bonnie++ ***"
case `uname` in
FreeBSD)
check newfs $LUN0
check mount -t ufs $LUN0 $MNTDIR
check cd $MNTDIR
check bonnie++ -u root -s 2g -r 1g -n0
check bonnie++ -u root -s 0
check cd -
check umount $MNTDIR
check fsck -t ufs $LUN0
check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3
check cd /$ZFSPOOL
check bonnie++ -u root -s 2g -r 1g -n0
check bonnie++ -u root -s 0
check cd -
check zpool destroy -f $ZFSPOOL
;;
Linux)
yes | check mkfs $LUN0
check mount $LUN0 $MNTDIR
check cd $MNTDIR
check bonnie++ -u root -s 2g -r 1g -n0
check bonnie++ -u root -s 0
check cd -
check umount $MNTDIR
check fsck -f $LUN0
check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3
check mount $LUN0 $MNTDIR
check cd $MNTDIR
check bonnie++ -u root -s 2g -r 1g -n0
check bonnie++ -u root -s 0
check cd -
check umount $MNTDIR
;;
SunOS)
yes | check newfs $LUN0
check mount -F ufs $LUN0 $MNTDIR
check cd $MNTDIR
check bonnie++ -u root -s 2g -r 1g -n0
check bonnie++ -u root -s 0
check cd -
check umount $MNTDIR
check fsck -yF ufs $LUN0
check zpool create -f $ZFSPOOL $LUN0 $LUN1
check cd /$ZFSPOOL
check bonnie++ -u root -s 2g -r 1g -n0
check bonnie++ -u root -s 0
check cd -
check zpool destroy -f $ZFSPOOL
;;
*)
die "unsupported system"
;;
esac
}
test_iozone() {
echo "*** iozone ***"
case `uname` in
FreeBSD)
check newfs $LUN0
check mount -t ufs $LUN0 $MNTDIR
check cd $MNTDIR
check iozone -a
check cd -
check umount $MNTDIR
check fsck -t ufs $LUN0
check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3
check cd /$ZFSPOOL
check iozone -a
check cd -
check zpool destroy -f $ZFSPOOL
;;
Linux)
yes | check mkfs $LUN0
check mount $LUN0 $MNTDIR
check cd $MNTDIR
check iozone -a
check cd -
check umount $MNTDIR
check fsck -f $LUN0
check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3
check mount $LUN0 $MNTDIR
check cd $MNTDIR
check iozone -a
check cd -
check umount $MNTDIR
;;
SunOS)
yes | check newfs $LUN0
check mount -F ufs $LUN0 $MNTDIR
check cd $MNTDIR
check iozone -a
check cd -
check umount $MNTDIR
check fsck -yF ufs $LUN0
check zpool create -f $ZFSPOOL $LUN0 $LUN1
check cd /$ZFSPOOL
check iozone -a
check cd -
check zpool destroy -f $ZFSPOOL
;;
*)
die "unsupported system"
;;
esac
}
test_postmark() {
echo "*** postmark ***"
case `uname` in
FreeBSD)
check newfs $LUN0
check mount -t ufs $LUN0 $MNTDIR
check cd $MNTDIR
printf "set number 10000\nrun\n" | check postmark
check cd -
check umount $MNTDIR
check fsck -t ufs $LUN0
check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3
check cd /$ZFSPOOL
printf "set number 10000\nrun\n" | check postmark
check cd -
check zpool destroy -f $ZFSPOOL
;;
Linux)
yes | check mkfs $LUN0
check mount $LUN0 $MNTDIR
check cd $MNTDIR
printf "set number 10000\nrun\n" | check postmark
check cd -
check umount $MNTDIR
check fsck -f $LUN0
check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3
check mount $LUN0 $MNTDIR
check cd $MNTDIR
printf "set number 10000\nrun\n" | check postmark
check cd -
check umount $MNTDIR
;;
SunOS)
yes | check newfs $LUN0
check mount -F ufs $LUN0 $MNTDIR
check cd $MNTDIR
printf "set number 10000\nrun\n" | check postmark
check cd -
check umount $MNTDIR
check fsck -yF ufs $LUN0
check zpool create -f $ZFSPOOL $LUN0 $LUN1
check cd /$ZFSPOOL
printf "set number 10000\nrun\n" | check postmark
check cd -
check zpool destroy -f $ZFSPOOL
;;
*)
die "unsupported system"
;;
esac
}
test_postgresql_freebsd() {
check newfs $LUN0
check mount -t ufs $LUN0 $MNTDIR
check chown pgsql $MNTDIR
check chmod 755 $MNTDIR
check cd /
# XXX: How to make 'check' work here?
su -m pgsql -c "initdb -D $MNTDIR/db"
su -m pgsql -c "pg_ctl -D $MNTDIR/db -l /tmp/log start"
check sleep 10
su -m pgsql -c "pgbench -i postgres"
su -m pgsql -c "pgbench -t 10000 postgres"
su -m pgsql -c "pg_ctl -D $MNTDIR/db stop"
check cd -
check umount $MNTDIR
check fsck -t ufs $LUN0
check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3
check chown pgsql /$ZFSPOOL
check chmod 755 /$ZFSPOOL
check cd /
# XXX: How to make 'check' work here?
su -m pgsql -c "initdb -D /$ZFSPOOL/db"
su -m pgsql -c "pg_ctl -D /$ZFSPOOL/db -l /tmp/log start"
check sleep 10
su -m pgsql -c "pgbench -i postgres"
su -m pgsql -c "pgbench -t 10000 postgres"
su -m pgsql -c "pg_ctl -D /$ZFSPOOL/db stop"
check cd -
check zpool destroy -f $ZFSPOOL
}
test_postgresql_linux() {
yes | check mkfs $LUN0
check mount $LUN0 $MNTDIR
check chown postgres $MNTDIR
check chmod 755 $MNTDIR
check cd /
# XXX: How to make 'check' work here?
su -m postgres -c "initdb -D $MNTDIR/db"
su -m postgres -c "pg_ctl -D $MNTDIR/db -l /tmp/log start"
check sleep 5
su -m postgres -c "pgbench -i"
su -m postgres -c "pgbench -t 10000"
su -m postgres -c "pg_ctl -D $MNTDIR/db stop"
check cd -
check umount $MNTDIR
check fsck -f $LUN0
check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3
check mount $LUN0 $MNTDIR
check chown postgres $MNTDIR
check chmod 755 $MNTDIR
check cd /
su -m postgres -c "initdb -D $MNTDIR/db"
su -m postgres -c "pg_ctl -D $MNTDIR/db -l /tmp/log start"
check sleep 5
su -m postgres -c "pgbench -i"
su -m postgres -c "pgbench -t 10000"
su -m postgres -c "pg_ctl -D $MNTDIR/db stop"
check cd -
check umount $MNTDIR
}
test_postgresql_solaris() {
PATH="$PATH:/usr/postgres/9.2-pgdg/bin" export PATH
yes | check newfs $LUN0
check mount -F ufs $LUN0 $MNTDIR
check chown postgres $MNTDIR
check chmod 755 $MNTDIR
check cd /
# XXX: How to make 'check' work here?
su postgres -c "initdb -D $MNTDIR/db"
su postgres -c "pg_ctl -D $MNTDIR/db -l /tmp/log start"
check sleep 10
su postgres -c "pgbench -i postgres"
su postgres -c "pgbench -t 10000 postgres"
su postgres -c "pg_ctl -D $MNTDIR/db stop"
check cd -
check umount $MNTDIR
check fsck -yF ufs $LUN0
check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3
check chown postgres /$ZFSPOOL
check chmod 755 /$ZFSPOOL
check cd /
# XXX: How to make 'check' work here?
su postgres -c "initdb -D /$ZFSPOOL/db"
su postgres -c "pg_ctl -D /$ZFSPOOL/db -l /tmp/log start"
check sleep 10
su postgres -c "pgbench -i postgres"
su postgres -c "pgbench -t 10000 postgres"
su postgres -c "pg_ctl -D /$ZFSPOOL/db stop"
check cd -
check zpool destroy -f $ZFSPOOL
}
test_postgresql() {
echo "*** postgresql ***"
case `uname` in
FreeBSD)
test_postgresql_freebsd
;;
Linux)
test_postgresql_linux
;;
SunOS)
test_postgresql_solaris
;;
*)
die "unsupported system"
;;
esac
}
test_detach() {
echo "*** detach ***"
case `uname` in
FreeBSD)
case `uname -r` in
9*)
echo "*** detaching not supported on FreeBSD 9 ***"
echo "*** please reboot the initiator VM before trying to run this script again ***"
;;
*)
check iscsictl -Ra
;;
esac
;;
Linux)
check iscsiadm -m node --logout
;;
SunOS)
check iscsiadm remove static-config $TARGET1,$TARGETIP
;;
*)
die "unsupported system"
;;
esac
}
banner
test_discovery
test_attach
test_newfs
test_cp
test_bonnie
test_iozone
test_postmark
test_postgresql
test_detach
echo "*** done ***"

View File

@ -68,6 +68,7 @@ SUBDIR= alias \
id \
ipcrm \
ipcs \
iscsictl \
join \
jot \
${_kdump} \

19
usr.bin/iscsictl/Makefile Normal file
View File

@ -0,0 +1,19 @@
# $FreeBSD$
PROG= iscsictl
SRCS= iscsictl.c periphs.c parse.y token.l y.tab.h
CFLAGS+= -I${.CURDIR}
CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi
MAN= iscsictl.8
DPADD= ${LIBCAM} ${LIBUTIL}
LDADD= -lcam -lfl -lutil
YFLAGS+= -v
LFLAGS+= -i
CLEANFILES= y.tab.c y.tab.h y.output
WARNS= 6
NO_WMISSING_VARIABLE_DECLARATIONS=
.include <bsd.prog.mk>

153
usr.bin/iscsictl/iscsictl.8 Normal file
View File

@ -0,0 +1,153 @@
.\" Copyright (c) 2012 The FreeBSD Foundation
.\" All rights reserved.
.\"
.\" This software was developed by Edward Tomasz Napierala under sponsorship
.\" from the FreeBSD Foundation.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd September 20, 2012
.Dt ISCSICTL 8
.Os
.Sh NAME
.Nm iscsictl
.Nd iSCSI initiator management utility
.Sh SYNOPSIS
.Nm
.Fl A
.Fl h Ar host Fl t Ar target Op Fl u Ar user Fl s Ar secret
.Nm
.Fl A
.Fl d Ar discovery-host Op Fl u Ar user Fl s Ar secret
.Nm
.Fl A
.Fl a Op Fl c Ar path
.Nm
.Fl A
.Fl n Ar nickname Op Fl c Ar path
.Nm
.Fl R
.Op Fl h Ar host
.Op Fl t Ar target
.Nm
.Fl R
.Fl a
.Nm
.Fl R
.Fl n Ar nickname Op Fl c Ar path
.Nm
.Fl L
.Op Fl v
.Sh DESCRIPTION
The
.Nm
utility is used to configure the iSCSI initiator.
.Pp
The following options are available:
.Bl -tag -width ".Fl A"
.It Fl A
Add session.
.It Fl R
Remove session.
.It Fl L
List sessions.
.It Fl a
When adding, add all sessions defined in the configuration file.
When removing, remove all currently established sessions.
.It Fl c
Path to the configuration file.
The default is
.Pa /etc/iscsi.conf .
.It Fl d
Target host name or address used for SendTargets discovery.
When used, it will add a temporary discovery session.
After discovery is done, sessions will be added for each discovered target,
and the temporary discovery sesion will be removed.
.It Fl h
Target host name or address for statically defined targets.
.It Fl n
The "nickname" of session defined in the configuration file.
.It Fl t
Target name.
.It Fl v
Verbose mode.
.El
.Pp
Since connecting to the target is performed in background, non-zero
exit status does not mean that the session was successfully established.
Use
.Nm Fl L
to check the connection status.
The initiator notifies
.Xr devd 8
when session gets connected or disconnected.
.Pp
Note that in order to the iSCSI initiator to be able to connect to a target,
the
.Xr iscsid 8
daemon must be running.
.Pp
Also note that
.Fx
currently supports two different initiators: the old one,
.Xr iscsi_initiator 4 ,
with its control utility
.Xr iscontrol 8 ,
and the new one,
.Xr iscsi 4 ,
with
.Xr iscsictl 8
and
.Xr iscsid 8 .
The only thing the two have in common is the configuration file,
.Xr iscsi.conf 5 .
.Sh FILES
.Bl -tag -width ".Pa /etc/iscsi.conf" -compact
.It Pa /etc/iscsi.conf
iSCSI initiator configuration file.
.El
.Sh EXIT STATUS
The
.Nm
utility exits 0 on success, and >0 if an error occurs.
.Sh EXAMPLES
Attach to target qn.2012-06.com.example:target0, served by 192.168.1.1:
.Dl Nm Fl A Fl t Ar qn.2012-06.com.example:target0 Fl h Ar 192.168.1.1
.Pp
Disconnect all iSCSI sessions:
.Dl Nm Fl Ra
.Sh SEE ALSO
.Xr iscsid 8 ,
.Xr iscsi.conf 5
.Sh HISTORY
The
.Nm
command appeared in
.Fx 10.0 .
.Sh AUTHORS
The
.Nm
was developed by
.An Edward Tomasz Napierala Aq trasz@FreeBSD.org
under sponsorship from the FreeBSD Foundation.

732
usr.bin/iscsictl/iscsictl.c Normal file
View File

@ -0,0 +1,732 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/ioctl.h>
#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iscsi_ioctl.h>
#include "iscsictl.h"
struct conf *
conf_new(void)
{
struct conf *conf;
conf = calloc(1, sizeof(*conf));
if (conf == NULL)
err(1, "calloc");
TAILQ_INIT(&conf->conf_targets);
return (conf);
}
struct target *
target_find(struct conf *conf, const char *nickname)
{
struct target *targ;
TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
if (targ->t_nickname != NULL &&
strcasecmp(targ->t_nickname, nickname) == 0)
return (targ);
}
return (NULL);
}
struct target *
target_new(struct conf *conf)
{
struct target *targ;
targ = calloc(1, sizeof(*targ));
if (targ == NULL)
err(1, "calloc");
targ->t_conf = conf;
TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
return (targ);
}
void
target_delete(struct target *targ)
{
TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
free(targ);
}
static char *
default_initiator_name(void)
{
char *name;
size_t namelen;
int error;
namelen = _POSIX_HOST_NAME_MAX + strlen(DEFAULT_IQN);
name = calloc(1, namelen + 1);
if (name == NULL)
err(1, "calloc");
strcpy(name, DEFAULT_IQN);
error = gethostname(name + strlen(DEFAULT_IQN),
namelen - strlen(DEFAULT_IQN));
if (error != 0)
err(1, "gethostname");
return (name);
}
static bool
valid_hex(const char ch)
{
switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'a':
case 'A':
case 'b':
case 'B':
case 'c':
case 'C':
case 'd':
case 'D':
case 'e':
case 'E':
case 'f':
case 'F':
return (true);
default:
return (false);
}
}
bool
valid_iscsi_name(const char *name)
{
int i;
if (strlen(name) >= MAX_NAME_LEN) {
warnx("overlong name for \"%s\"; max length allowed "
"by iSCSI specification is %d characters",
name, MAX_NAME_LEN);
return (false);
}
/*
* In the cases below, we don't return an error, just in case the admin
* was right, and we're wrong.
*/
if (strncasecmp(name, "iqn.", strlen("iqn.")) == 0) {
for (i = strlen("iqn."); name[i] != '\0'; i++) {
/*
* XXX: We should verify UTF-8 normalisation, as defined
* by 3.2.6.2: iSCSI Name Encoding.
*/
if (isalnum(name[i]))
continue;
if (name[i] == '-' || name[i] == '.' || name[i] == ':')
continue;
warnx("invalid character \"%c\" in iSCSI name "
"\"%s\"; allowed characters are letters, digits, "
"'-', '.', and ':'", name[i], name);
break;
}
/*
* XXX: Check more stuff: valid date and a valid reversed domain.
*/
} else if (strncasecmp(name, "eui.", strlen("eui.")) == 0) {
if (strlen(name) != strlen("eui.") + 16)
warnx("invalid iSCSI name \"%s\"; the \"eui.\" "
"should be followed by exactly 16 hexadecimal "
"digits", name);
for (i = strlen("eui."); name[i] != '\0'; i++) {
if (!valid_hex(name[i])) {
warnx("invalid character \"%c\" in iSCSI "
"name \"%s\"; allowed characters are 1-9 "
"and A-F", name[i], name);
break;
}
}
} else if (strncasecmp(name, "naa.", strlen("naa.")) == 0) {
if (strlen(name) > strlen("naa.") + 32)
warnx("invalid iSCSI name \"%s\"; the \"naa.\" "
"should be followed by at most 32 hexadecimal "
"digits", name);
for (i = strlen("naa."); name[i] != '\0'; i++) {
if (!valid_hex(name[i])) {
warnx("invalid character \"%c\" in ISCSI "
"name \"%s\"; allowed characters are 1-9 "
"and A-F", name[i], name);
break;
}
}
} else {
warnx("invalid iSCSI name \"%s\"; should start with "
"either \".iqn\", \"eui.\", or \"naa.\"",
name);
}
return (true);
}
void
conf_verify(struct conf *conf)
{
struct target *targ;
TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
assert(targ->t_nickname != NULL);
if (targ->t_session_type == SESSION_TYPE_UNSPECIFIED)
targ->t_session_type = SESSION_TYPE_NORMAL;
if (targ->t_session_type == SESSION_TYPE_NORMAL &&
targ->t_name == NULL)
errx(1, "missing TargetName for target \"%s\"",
targ->t_nickname);
if (targ->t_session_type == SESSION_TYPE_DISCOVERY &&
targ->t_name != NULL)
errx(1, "cannot specify TargetName for discovery "
"sessions for target \"%s\"", targ->t_nickname);
if (targ->t_name != NULL) {
if (valid_iscsi_name(targ->t_name) == false)
errx(1, "invalid target name \"%s\"",
targ->t_name);
}
if (targ->t_protocol == PROTOCOL_UNSPECIFIED)
targ->t_protocol = PROTOCOL_ISCSI;
#ifndef ICL_KERNEL_PROXY
if (targ->t_protocol == PROTOCOL_ISER)
errx(1, "iSER support requires ICL_KERNEL_PROXY; "
"see iscsi(4) for details");
#endif
if (targ->t_address == NULL)
errx(1, "missing TargetAddress for target \"%s\"",
targ->t_nickname);
if (targ->t_initiator_name == NULL)
targ->t_initiator_name = default_initiator_name();
if (valid_iscsi_name(targ->t_initiator_name) == false)
errx(1, "invalid initiator name \"%s\"",
targ->t_initiator_name);
if (targ->t_header_digest == DIGEST_UNSPECIFIED)
targ->t_header_digest = DIGEST_NONE;
if (targ->t_data_digest == DIGEST_UNSPECIFIED)
targ->t_data_digest = DIGEST_NONE;
if (targ->t_auth_method == AUTH_METHOD_UNSPECIFIED) {
if (targ->t_user != NULL || targ->t_secret != NULL ||
targ->t_mutual_user != NULL ||
targ->t_mutual_secret != NULL)
targ->t_auth_method =
AUTH_METHOD_CHAP;
else
targ->t_auth_method =
AUTH_METHOD_NONE;
}
if (targ->t_auth_method == AUTH_METHOD_CHAP) {
if (targ->t_user == NULL) {
errx(1, "missing chapIName for target \"%s\"",
targ->t_nickname);
}
if (targ->t_secret == NULL)
errx(1, "missing chapSecret for target \"%s\"",
targ->t_nickname);
if (targ->t_mutual_user != NULL ||
targ->t_mutual_secret != NULL) {
if (targ->t_mutual_user == NULL)
errx(1, "missing tgtChapName for "
"target \"%s\"", targ->t_nickname);
if (targ->t_mutual_secret == NULL)
errx(1, "missing tgtChapSecret for "
"target \"%s\"", targ->t_nickname);
}
}
}
}
static void
conf_from_target(struct iscsi_session_conf *conf,
const struct target *targ)
{
memset(conf, 0, sizeof(*conf));
/*
* XXX: Check bounds and return error instead of silently truncating.
*/
if (targ->t_initiator_name != NULL)
strlcpy(conf->isc_initiator, targ->t_initiator_name,
sizeof(conf->isc_initiator));
if (targ->t_initiator_address != NULL)
strlcpy(conf->isc_initiator_addr, targ->t_initiator_address,
sizeof(conf->isc_initiator_addr));
if (targ->t_initiator_alias != NULL)
strlcpy(conf->isc_initiator_alias, targ->t_initiator_alias,
sizeof(conf->isc_initiator_alias));
if (targ->t_name != NULL)
strlcpy(conf->isc_target, targ->t_name,
sizeof(conf->isc_target));
if (targ->t_address != NULL)
strlcpy(conf->isc_target_addr, targ->t_address,
sizeof(conf->isc_target_addr));
if (targ->t_user != NULL)
strlcpy(conf->isc_user, targ->t_user,
sizeof(conf->isc_user));
if (targ->t_secret != NULL)
strlcpy(conf->isc_secret, targ->t_secret,
sizeof(conf->isc_secret));
if (targ->t_mutual_user != NULL)
strlcpy(conf->isc_mutual_user, targ->t_mutual_user,
sizeof(conf->isc_mutual_user));
if (targ->t_mutual_secret != NULL)
strlcpy(conf->isc_mutual_secret, targ->t_mutual_secret,
sizeof(conf->isc_mutual_secret));
if (targ->t_session_type == SESSION_TYPE_DISCOVERY)
conf->isc_discovery = 1;
if (targ->t_protocol == PROTOCOL_ISER)
conf->isc_iser = 1;
if (targ->t_header_digest == DIGEST_CRC32C)
conf->isc_header_digest = ISCSI_DIGEST_CRC32C;
else
conf->isc_header_digest = ISCSI_DIGEST_NONE;
if (targ->t_data_digest == DIGEST_CRC32C)
conf->isc_data_digest = ISCSI_DIGEST_CRC32C;
else
conf->isc_data_digest = ISCSI_DIGEST_NONE;
}
static int
kernel_add(int iscsi_fd, const struct target *targ)
{
struct iscsi_session_add isa;
int error;
memset(&isa, 0, sizeof(isa));
conf_from_target(&isa.isa_conf, targ);
error = ioctl(iscsi_fd, ISCSISADD, &isa);
if (error != 0)
warn("ISCSISADD");
return (error);
}
static int
kernel_remove(int iscsi_fd, const struct target *targ)
{
struct iscsi_session_remove isr;
int error;
memset(&isr, 0, sizeof(isr));
conf_from_target(&isr.isr_conf, targ);
error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
if (error != 0)
warn("ISCSISREMOVE");
return (error);
}
/*
* XXX: Add filtering.
*/
static int
kernel_list(int iscsi_fd, const struct target *targ __unused,
int verbose)
{
struct iscsi_session_state *states = NULL;
const struct iscsi_session_state *state;
const struct iscsi_session_conf *conf;
struct iscsi_session_list isl;
unsigned int i, nentries = 1;
int error;
bool show_periphs;
for (;;) {
states = realloc(states,
nentries * sizeof(struct iscsi_session_state));
if (states == NULL)
err(1, "realloc");
memset(&isl, 0, sizeof(isl));
isl.isl_nentries = nentries;
isl.isl_pstates = states;
error = ioctl(iscsi_fd, ISCSISLIST, &isl);
if (error != 0 && errno == EMSGSIZE) {
nentries *= 4;
continue;
}
break;
}
if (error != 0) {
warn("ISCSISLIST");
return (error);
}
if (verbose != 0) {
for (i = 0; i < isl.isl_nentries; i++) {
state = &states[i];
conf = &state->iss_conf;
printf("Session ID: %d\n", state->iss_id);
printf("Initiator name: %s\n", conf->isc_initiator);
printf("Initiator addr: %s\n",
conf->isc_initiator_addr);
printf("Initiator alias: %s\n",
conf->isc_initiator_alias);
printf("Target name: %s\n", conf->isc_target);
printf("Target addr: %s\n",
conf->isc_target_addr);
printf("Target alias: %s\n",
state->iss_target_alias);
printf("User: %s\n", conf->isc_user);
printf("Secret: %s\n", conf->isc_secret);
printf("Mutual user: %s\n",
conf->isc_mutual_user);
printf("Mutual secret: %s\n",
conf->isc_mutual_secret);
printf("Session type: %s\n",
conf->isc_discovery ? "Discovery" : "Normal");
printf("Session state: %s\n",
state->iss_connected ?
"Connected" : "Disconnected");
printf("Failure reason: %s\n", state->iss_reason);
printf("Header digest: %s\n",
state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
"CRC32C" : "None");
printf("Data digest: %s\n",
state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
"CRC32C" : "None");
printf("DataSegmentLen: %d\n",
state->iss_max_data_segment_length);
printf("ImmediateData: %s\n",
state->iss_immediate_data ? "Yes" : "No");
printf("iSER (RDMA): %s\n",
conf->isc_iser ? "Yes" : "No");
printf("Device nodes: ");
print_periphs(state->iss_id);
printf("\n\n");
}
} else {
printf("%-36s %-16s %s\n",
"Target name", "Target addr", "State");
for (i = 0; i < isl.isl_nentries; i++) {
state = &states[i];
conf = &state->iss_conf;
show_periphs = false;
printf("%-36s %-16s ",
conf->isc_target, conf->isc_target_addr);
if (state->iss_reason[0] != '\0') {
printf("%s\n", state->iss_reason);
} else {
if (conf->isc_discovery) {
printf("Discovery\n");
} else if (state->iss_connected) {
printf("Connected: ");
print_periphs(state->iss_id);
printf("\n");
} else {
printf("Disconnected\n");
}
}
}
}
return (0);
}
static void
usage(void)
{
fprintf(stderr, "usage: iscsictl -A -h host -t target "
"[-u user -s secret]\n");
fprintf(stderr, " iscsictl -A -d discovery-host "
"[-u user -s secret]\n");
fprintf(stderr, " iscsictl -A -a [-c path]\n");
fprintf(stderr, " iscsictl -A -n nickname [-c path]\n");
fprintf(stderr, " iscsictl -R [-h host] [-t target]\n");
fprintf(stderr, " iscsictl -R -a\n");
fprintf(stderr, " iscsictl -R -n nickname [-c path]\n");
fprintf(stderr, " iscsictl -L [-v]\n");
exit(1);
}
char *
checked_strdup(const char *s)
{
char *c;
c = strdup(s);
if (c == NULL)
err(1, "strdup");
return (c);
}
int
main(int argc, char **argv)
{
int Aflag = 0, Rflag = 0, Lflag = 0, aflag = 0, vflag = 0;
const char *conf_path = DEFAULT_CONFIG_PATH;
char *nickname = NULL, *discovery_host = NULL, *host = NULL,
*target = NULL, *user = NULL, *secret = NULL;
int ch, error, iscsi_fd;
int failed = 0;
struct conf *conf;
struct target *targ;
while ((ch = getopt(argc, argv, "ARLac:d:n:h:t:u:s:v")) != -1) {
switch (ch) {
case 'A':
Aflag = 1;
break;
case 'R':
Rflag = 1;
break;
case 'L':
Lflag = 1;
break;
case 'a':
aflag = 1;
break;
case 'c':
conf_path = optarg;
break;
case 'd':
discovery_host = optarg;
break;
case 'n':
nickname = optarg;
break;
case 'h':
host = optarg;
break;
case 't':
target = optarg;
break;
case 'u':
user = optarg;
break;
case 's':
secret = optarg;
break;
case 'v':
vflag = 1;
break;
case '?':
default:
usage();
}
}
argc -= optind;
if (argc != 0)
usage();
if (Aflag + Rflag + Lflag == 0)
Lflag = 1;
if (Aflag + Rflag + Lflag > 1)
errx(1, "at most one of -A, -R, or -L may be specified");
/*
* Note that we ignore unneccessary/inapplicable "-c" flag; so that
* people can do something like "alias ISCSICTL="iscsictl -c path"
* in shell scripts.
*/
if (Aflag != 0) {
if (aflag != 0) {
if (host != NULL)
errx(1, "-a and -h and mutually exclusive");
if (target != NULL)
errx(1, "-a and -t and mutually exclusive");
if (user != NULL)
errx(1, "-a and -u and mutually exclusive");
if (secret != NULL)
errx(1, "-a and -s and mutually exclusive");
if (nickname != NULL)
errx(1, "-a and -n and mutually exclusive");
if (discovery_host != NULL)
errx(1, "-a and -d and mutually exclusive");
} else if (nickname != NULL) {
if (host != NULL)
errx(1, "-n and -h and mutually exclusive");
if (target != NULL)
errx(1, "-n and -t and mutually exclusive");
if (user != NULL)
errx(1, "-n and -u and mutually exclusive");
if (secret != NULL)
errx(1, "-n and -s and mutually exclusive");
if (discovery_host != NULL)
errx(1, "-n and -d and mutually exclusive");
} else if (discovery_host != NULL) {
if (host != NULL)
errx(1, "-d and -h and mutually exclusive");
if (target != NULL)
errx(1, "-d and -t and mutually exclusive");
} else {
if (target == NULL && host == NULL)
errx(1, "must specify -a, -n or -t/-h");
if (target != NULL && host == NULL)
errx(1, "-t must always be used with -h");
if (host != NULL && target == NULL)
errx(1, "-h must always be used with -t");
}
if (user != NULL && secret == NULL)
errx(1, "-u must always be used with -s");
if (secret != NULL && user == NULL)
errx(1, "-s must always be used with -u");
if (vflag != 0)
errx(1, "-v cannot be used with -A");
} else if (Rflag != 0) {
if (user != NULL)
errx(1, "-R and -u are mutually exclusive");
if (secret != NULL)
errx(1, "-R and -s are mutually exclusive");
if (discovery_host != NULL)
errx(1, "-R and -d are mutually exclusive");
if (aflag != 0) {
if (host != NULL)
errx(1, "-a and -h and mutually exclusive");
if (target != NULL)
errx(1, "-a and -t and mutually exclusive");
if (nickname != NULL)
errx(1, "-a and -n and mutually exclusive");
} else if (nickname != NULL) {
if (host != NULL)
errx(1, "-n and -h and mutually exclusive");
if (target != NULL)
errx(1, "-n and -t and mutually exclusive");
} else if (host != NULL) {
if (target != NULL)
errx(1, "-h and -t and mutually exclusive");
} else if (target != NULL) {
if (host != NULL)
errx(1, "-t and -h and mutually exclusive");
} else
errx(1, "must specify either-a, -n, -t, or -h");
if (vflag != 0)
errx(1, "-v cannot be used with -R");
} else {
assert(Lflag != 0);
if (host != NULL)
errx(1, "-L and -h and mutually exclusive");
if (target != NULL)
errx(1, "-L and -t and mutually exclusive");
if (user != NULL)
errx(1, "-L and -u and mutually exclusive");
if (secret != NULL)
errx(1, "-L and -s and mutually exclusive");
if (nickname != NULL)
errx(1, "-L and -n and mutually exclusive");
if (discovery_host != NULL)
errx(1, "-L and -d and mutually exclusive");
}
iscsi_fd = open(ISCSI_PATH, O_RDWR);
if (iscsi_fd < 0)
err(1, "failed to open %s", ISCSI_PATH);
if (Aflag != 0 && aflag != 0) {
conf = conf_new_from_file(conf_path);
TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
failed += kernel_add(iscsi_fd, targ);
} else if (nickname != NULL) {
conf = conf_new_from_file(conf_path);
targ = target_find(conf, nickname);
if (targ == NULL)
errx(1, "target %s not found in the configuration file",
nickname);
if (Aflag != 0)
failed += kernel_add(iscsi_fd, targ);
else if (Rflag != 0)
failed += kernel_remove(iscsi_fd, targ);
else
failed += kernel_list(iscsi_fd, targ, vflag);
} else {
if (Aflag != 0 && target != NULL) {
if (valid_iscsi_name(target) == false)
errx(1, "invalid target name \"%s\"", target);
}
conf = conf_new();
targ = target_new(conf);
targ->t_initiator_name = default_initiator_name();
targ->t_header_digest = DIGEST_NONE;
targ->t_data_digest = DIGEST_NONE;
targ->t_name = target;
if (discovery_host != NULL) {
targ->t_session_type = SESSION_TYPE_DISCOVERY;
targ->t_address = discovery_host;
} else {
targ->t_session_type = SESSION_TYPE_NORMAL;
targ->t_address = host;
}
targ->t_user = user;
targ->t_secret = secret;
if (Aflag != 0)
failed += kernel_add(iscsi_fd, targ);
else if (Rflag != 0)
failed += kernel_remove(iscsi_fd, targ);
else
failed += kernel_list(iscsi_fd, targ, vflag);
}
error = close(iscsi_fd);
if (error != 0)
err(1, "close");
if (failed > 0)
return (1);
return (0);
}

116
usr.bin/iscsictl/iscsictl.h Normal file
View File

@ -0,0 +1,116 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef ISCSICTL_H
#define ISCSICTL_H
#include <sys/queue.h>
#include <stdbool.h>
#include <libutil.h>
#define DEFAULT_CONFIG_PATH "/etc/iscsi.conf"
#define DEFAULT_IQN "iqn.1994-09.org.freebsd:"
#define MAX_NAME_LEN 223
#define MAX_DATA_SEGMENT_LENGTH 65536
#define AUTH_METHOD_UNSPECIFIED 0
#define AUTH_METHOD_NONE 1
#define AUTH_METHOD_CHAP 2
#define DIGEST_UNSPECIFIED 0
#define DIGEST_NONE 1
#define DIGEST_CRC32C 2
#define SESSION_TYPE_UNSPECIFIED 0
#define SESSION_TYPE_NORMAL 1
#define SESSION_TYPE_DISCOVERY 2
#define PROTOCOL_UNSPECIFIED 0
#define PROTOCOL_ISCSI 1
#define PROTOCOL_ISER 2
struct target {
TAILQ_ENTRY(target) t_next;
struct conf *t_conf;
char *t_nickname;
char *t_name;
char *t_address;
char *t_initiator_name;
char *t_initiator_address;
char *t_initiator_alias;
int t_header_digest;
int t_data_digest;
int t_auth_method;
int t_session_type;
int t_protocol;
char *t_user;
char *t_secret;
char *t_mutual_user;
char *t_mutual_secret;
};
struct conf {
TAILQ_HEAD(, target) conf_targets;
};
#define CONN_SESSION_TYPE_NONE 0
#define CONN_SESSION_TYPE_DISCOVERY 1
#define CONN_SESSION_TYPE_NORMAL 2
struct connection {
struct target *conn_target;
int conn_socket;
int conn_session_type;
uint32_t conn_cmdsn;
uint32_t conn_statsn;
size_t conn_max_data_segment_length;
size_t conn_max_burst_length;
size_t conn_max_outstanding_r2t;
int conn_header_digest;
int conn_data_digest;
};
struct conf *conf_new(void);
struct conf *conf_new_from_file(const char *path);
void conf_delete(struct conf *conf);
void conf_verify(struct conf *conf);
struct target *target_new(struct conf *conf);
struct target *target_find(struct conf *conf, const char *nickname);
void target_delete(struct target *ic);
void print_periphs(int session_id);
char *checked_strdup(const char *);
bool valid_iscsi_name(const char *name);
#endif /* !ISCSICTL_H */

333
usr.bin/iscsictl/parse.y Normal file
View File

@ -0,0 +1,333 @@
%{
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <err.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "iscsictl.h"
extern FILE *yyin;
extern char *yytext;
extern int lineno;
static struct conf *conf;
static struct target *target;
extern void yyerror(const char *);
extern int yylex(void);
extern void yyrestart(FILE *);
%}
%token AUTH_METHOD HEADER_DIGEST DATA_DIGEST TARGET_NAME TARGET_ADDRESS
%token INITIATOR_NAME INITIATOR_ADDRESS INITIATOR_ALIAS USER SECRET
%token MUTUAL_USER MUTUAL_SECRET SESSION_TYPE PROTOCOL IGNORED
%token EQUALS OPENING_BRACKET CLOSING_BRACKET
%union
{
char *str;
}
%token <str> STR
%%
statements:
|
statements target_statement
;
target_statement: STR OPENING_BRACKET target_entries CLOSING_BRACKET
{
if (target_find(conf, $1) != NULL)
errx(1, "duplicated target %s", $1);
target->t_nickname = $1;
target = target_new(conf);
}
;
target_entries:
|
target_entries target_entry
;
target_entry:
target_name_statement
|
target_address_statement
|
initiator_name_statement
|
initiator_address_statement
|
initiator_alias_statement
|
user_statement
|
secret_statement
|
mutual_user_statement
|
mutual_secret_statement
|
auth_method_statement
|
header_digest_statement
|
data_digest_statement
|
session_type_statement
|
protocol_statement
|
ignored_statement
;
target_name_statement: TARGET_NAME EQUALS STR
{
if (target->t_name != NULL)
errx(1, "duplicated TargetName at line %d", lineno + 1);
target->t_name = $3;
}
;
target_address_statement: TARGET_ADDRESS EQUALS STR
{
if (target->t_address != NULL)
errx(1, "duplicated TargetAddress at line %d", lineno + 1);
target->t_address = $3;
}
;
initiator_name_statement: INITIATOR_NAME EQUALS STR
{
if (target->t_initiator_name != NULL)
errx(1, "duplicated InitiatorName at line %d", lineno + 1);
target->t_initiator_name = $3;
}
;
initiator_address_statement: INITIATOR_ADDRESS EQUALS STR
{
if (target->t_initiator_address != NULL)
errx(1, "duplicated InitiatorAddress at line %d", lineno + 1);
target->t_initiator_address = $3;
}
;
initiator_alias_statement: INITIATOR_ALIAS EQUALS STR
{
if (target->t_initiator_alias != NULL)
errx(1, "duplicated InitiatorAlias at line %d", lineno + 1);
target->t_initiator_alias = $3;
}
;
user_statement: USER EQUALS STR
{
if (target->t_user != NULL)
errx(1, "duplicated chapIName at line %d", lineno + 1);
target->t_user = $3;
}
;
secret_statement: SECRET EQUALS STR
{
if (target->t_secret != NULL)
errx(1, "duplicated chapSecret at line %d", lineno + 1);
target->t_secret = $3;
}
;
mutual_user_statement: MUTUAL_USER EQUALS STR
{
if (target->t_mutual_user != NULL)
errx(1, "duplicated tgtChapName at line %d", lineno + 1);
target->t_mutual_user = $3;
}
;
mutual_secret_statement:MUTUAL_SECRET EQUALS STR
{
if (target->t_mutual_secret != NULL)
errx(1, "duplicated tgtChapSecret at line %d", lineno + 1);
target->t_mutual_secret = $3;
}
;
auth_method_statement: AUTH_METHOD EQUALS STR
{
if (target->t_auth_method != AUTH_METHOD_UNSPECIFIED)
errx(1, "duplicated AuthMethod at line %d", lineno + 1);
if (strcasecmp($3, "none") == 0)
target->t_auth_method = AUTH_METHOD_NONE;
else if (strcasecmp($3, "chap") == 0)
target->t_auth_method = AUTH_METHOD_CHAP;
else
errx(1, "invalid AuthMethod at line %d; "
"must be either \"none\" or \"CHAP\"", lineno + 1);
}
;
header_digest_statement: HEADER_DIGEST EQUALS STR
{
if (target->t_header_digest != DIGEST_UNSPECIFIED)
errx(1, "duplicated HeaderDigest at line %d", lineno + 1);
if (strcasecmp($3, "none") == 0)
target->t_header_digest = DIGEST_NONE;
else if (strcasecmp($3, "CRC32C") == 0)
target->t_header_digest = DIGEST_CRC32C;
else
errx(1, "invalid HeaderDigest at line %d; "
"must be either \"none\" or \"CRC32C\"", lineno + 1);
}
;
data_digest_statement: DATA_DIGEST EQUALS STR
{
if (target->t_data_digest != DIGEST_UNSPECIFIED)
errx(1, "duplicated DataDigest at line %d", lineno + 1);
if (strcasecmp($3, "none") == 0)
target->t_data_digest = DIGEST_NONE;
else if (strcasecmp($3, "CRC32C") == 0)
target->t_data_digest = DIGEST_CRC32C;
else
errx(1, "invalid DataDigest at line %d; "
"must be either \"none\" or \"CRC32C\"", lineno + 1);
}
;
session_type_statement: SESSION_TYPE EQUALS STR
{
if (target->t_session_type != SESSION_TYPE_UNSPECIFIED)
errx(1, "duplicated SessionType at line %d", lineno + 1);
if (strcasecmp($3, "normal") == 0)
target->t_session_type = SESSION_TYPE_NORMAL;
else if (strcasecmp($3, "discovery") == 0)
target->t_session_type = SESSION_TYPE_DISCOVERY;
else
errx(1, "invalid SessionType at line %d; "
"must be either \"normal\" or \"discovery\"", lineno + 1);
}
;
protocol_statement: PROTOCOL EQUALS STR
{
if (target->t_protocol != PROTOCOL_UNSPECIFIED)
errx(1, "duplicated protocol at line %d", lineno + 1);
if (strcasecmp($3, "iscsi") == 0)
target->t_protocol = PROTOCOL_ISCSI;
else if (strcasecmp($3, "iser") == 0)
target->t_protocol = PROTOCOL_ISER;
else
errx(1, "invalid protocol at line %d; "
"must be either \"iscsi\" or \"iser\"", lineno + 1);
}
;
ignored_statement: IGNORED EQUALS STR
{
warnx("obsolete statement ignored at line %d", lineno + 1);
}
;
%%
void
yyerror(const char *str)
{
errx(1, "error in configuration file at line %d near '%s': %s",
lineno + 1, yytext, str);
}
static void
check_perms(const char *path)
{
struct stat sb;
int error;
error = stat(path, &sb);
if (error != 0) {
warn("stat");
return;
}
if (sb.st_mode & S_IWOTH) {
warnx("%s is world-writable", path);
} else if (sb.st_mode & S_IROTH) {
warnx("%s is world-readable", path);
} else if (sb.st_mode & S_IXOTH) {
/*
* Ok, this one doesn't matter, but still do it,
* just for consistency.
*/
warnx("%s is world-executable", path);
}
/*
* XXX: Should we also check for owner != 0?
*/
}
struct conf *
conf_new_from_file(const char *path)
{
int error;
conf = conf_new();
target = target_new(conf);
yyin = fopen(path, "r");
if (yyin == NULL)
err(1, "unable to open configuration file %s", path);
check_perms(path);
lineno = 0;
yyrestart(yyin);
error = yyparse();
assert(error == 0);
fclose(yyin);
assert(target->t_nickname == NULL);
target_delete(target);
conf_verify(conf);
return (conf);
}

186
usr.bin/iscsictl/periphs.c Normal file
View File

@ -0,0 +1,186 @@
/*
* Copyright (c) 1997-2007 Kenneth D. Merry
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Edward Tomasz Napierala
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/ioctl.h>
#include <sys/stdint.h>
#include <sys/types.h>
#include <sys/endian.h>
#include <sys/sbuf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include <limits.h>
#include <fcntl.h>
#include <err.h>
#include <cam/cam.h>
#include <cam/cam_debug.h>
#include <cam/cam_ccb.h>
#include <cam/scsi/scsi_all.h>
#include <cam/scsi/scsi_da.h>
#include <cam/scsi/scsi_pass.h>
#include <cam/scsi/scsi_message.h>
#include <cam/scsi/smp_all.h>
#include <cam/ata/ata_all.h>
#include <camlib.h>
#include "iscsictl.h"
void
print_periphs(int session_id)
{
union ccb ccb;
int bufsize, fd;
unsigned int i;
int skip_bus, skip_device;
if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) {
warn("couldn't open %s", XPT_DEVICE);
return;
}
/*
* First, iterate over the whole list to find the bus.
*/
bzero(&ccb, sizeof(union ccb));
ccb.ccb_h.path_id = CAM_XPT_PATH_ID;
ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;
ccb.ccb_h.func_code = XPT_DEV_MATCH;
bufsize = sizeof(struct dev_match_result) * 100;
ccb.cdm.match_buf_len = bufsize;
ccb.cdm.matches = (struct dev_match_result *)malloc(bufsize);
if (ccb.cdm.matches == NULL) {
warnx("can't malloc memory for matches");
close(fd);
return;
}
ccb.cdm.num_matches = 0;
/*
* We fetch all nodes, since we display most of them in the default
* case, and all in the verbose case.
*/
ccb.cdm.num_patterns = 0;
ccb.cdm.pattern_buf_len = 0;
/*
* We do the ioctl multiple times if necessary, in case there are
* more than 100 nodes in the EDT.
*/
do {
if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) {
warn("error sending CAMIOCOMMAND ioctl");
break;
}
if ((ccb.ccb_h.status != CAM_REQ_CMP)
|| ((ccb.cdm.status != CAM_DEV_MATCH_LAST)
&& (ccb.cdm.status != CAM_DEV_MATCH_MORE))) {
warnx("got CAM error %#x, CDM error %d\n",
ccb.ccb_h.status, ccb.cdm.status);
break;
}
skip_bus = 1;
skip_device = 1;
for (i = 0; i < ccb.cdm.num_matches; i++) {
switch (ccb.cdm.matches[i].type) {
case DEV_MATCH_BUS: {
struct bus_match_result *bus_result;
bus_result = &ccb.cdm.matches[i].result.bus_result;
skip_bus = 1;
if (strcmp(bus_result->dev_name, "iscsi") != 0) {
//printf("not iscsi\n");
continue;
}
if ((int)bus_result->unit_number != session_id) {
//printf("wrong unit, %d != %d\n", bus_result->unit_number, session_id);
continue;
}
skip_bus = 0;
}
case DEV_MATCH_DEVICE: {
skip_device = 1;
if (skip_bus != 0)
continue;
skip_device = 0;
break;
}
case DEV_MATCH_PERIPH: {
struct periph_match_result *periph_result;
periph_result =
&ccb.cdm.matches[i].result.periph_result;
if (skip_device != 0)
continue;
if (strcmp(periph_result->periph_name, "pass") == 0)
continue;
fprintf(stdout, "%s%d ",
periph_result->periph_name,
periph_result->unit_number);
break;
}
default:
fprintf(stdout, "unknown match type\n");
break;
}
}
} while ((ccb.ccb_h.status == CAM_REQ_CMP)
&& (ccb.cdm.status == CAM_DEV_MATCH_MORE));
close(fd);
}

93
usr.bin/iscsictl/token.l Normal file
View File

@ -0,0 +1,93 @@
%{
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "iscsictl.h"
#include "y.tab.h"
int lineno;
#define YY_DECL int yylex(void)
extern int yylex(void);
%}
%option noinput
%option nounput
%%
HeaderDigest { return HEADER_DIGEST; }
DataDigest { return DATA_DIGEST; }
TargetName { return TARGET_NAME; }
TargetAddress { return TARGET_ADDRESS; }
InitiatorName { return INITIATOR_NAME; }
InitiatorAddress { return INITIATOR_ADDRESS; }
InitiatorAlias { return INITIATOR_ALIAS; }
chapIName { return USER; }
chapSecret { return SECRET; }
tgtChapName { return MUTUAL_USER; }
tgtChapSecret { return MUTUAL_SECRET; }
AuthMethod { return AUTH_METHOD; }
SessionType { return SESSION_TYPE; }
protocol { return PROTOCOL; }
port { return IGNORED; }
MaxConnections { return IGNORED; }
TargetAlias { return IGNORED; }
TargetPortalGroupTag { return IGNORED; }
InitialR2T { return IGNORED; }
ImmediateData { return IGNORED; }
MaxRecvDataSegmentLength { return IGNORED; }
MaxBurstLength { return IGNORED; }
FirstBurstLength { return IGNORED; }
DefaultTime2Wait { return IGNORED; }
DefaultTime2Retain { return IGNORED; }
MaxOutstandingR2T { return IGNORED; }
DataPDUInOrder { return IGNORED; }
DataSequenceInOrder { return IGNORED; }
ErrorRecoveryLevel { return IGNORED; }
tags { return IGNORED; }
maxluns { return IGNORED; }
sockbufsize { return IGNORED; }
chapDigest { return IGNORED; }
\"[^"]+\" { yylval.str = strndup(yytext + 1,
strlen(yytext) - 2); return STR; }
[a-zA-Z0-9\.\-_/\:\[\]]+ { yylval.str = strdup(yytext); return STR; }
\{ { return OPENING_BRACKET; }
\} { return CLOSING_BRACKET; }
= { return EQUALS; }
#.*$ /* ignore comments */;
\n { lineno++; }
[ \t]+ /* ignore whitespace */;
%%

View File

@ -17,6 +17,7 @@ SUBDIR= adduser \
crashinfo \
cron \
ctladm \
ctld \
daemon \
dconschat \
devinfo \
@ -35,6 +36,7 @@ SUBDIR= adduser \
ifmcstat \
inetd \
iostat \
iscsid \
isfctl \
kldxref \
mailwrapper \

View File

@ -197,6 +197,16 @@
.Nm
.Ic dumpstructs
.Nm
.Ic islist
.Op Fl v
.Op Fl x
.Nm
.Ic islogout
.Aq Fl a | Fl h Ar host | Fl c Ar connection-id | Fl i Ar name
.Nm
.Ic isterminate
.Aq Fl a | Fl h Ar host | Fl c Ar connection-id | Fl i Ar name
.Nm
.Ic help
.Sh DESCRIPTION
The
@ -883,6 +893,41 @@ If you specify
.Fl x ,
the entire LUN database is displayed in XML format.
.El
.It Ic islist
Get a list of currently running iSCSI connections.
This includes initiator and target names and the unique connection IDs.
.Bl -tag -width 11n
.It Fl v
Verbose mode.
.It Fl x
Dump the raw XML.
The connections list information from the kernel comes in XML format, and this
option allows the display of the raw XML data.
.El
.It Ic islogout
Ask the initiator to log out iSCSI connections matching criteria.
.Bl -tag -width 11n
.It Fl a
Log out all connections.
.It Fl h
Specify initiator IP address.
.It Fl c
Specify connection ID.
.It Fl i
Specify initiator name.
.El
.It Ic isterminate
Forcibly terminate iSCSI connections matching criteria.
.Bl -tag -width 11n
.It Fl a
Terminate all connections.
.It Fl h
Specify initiator IP address.
.It Fl c
Specify connection ID.
.It Fl i
Specify initiator name.
.El
.It Ic help
Display
.Nm
@ -977,7 +1022,8 @@ This will result in a sense key of NOT READY (0x02), and an ASC/ASCQ of
.Xr cam 4 ,
.Xr ctl 4 ,
.Xr xpt 4 ,
.Xr camcontrol 8
.Xr camcontrol 8 ,
.Xr ctld 8
.Sh HISTORY
The
.Nm

View File

@ -117,7 +117,10 @@ typedef enum {
CTLADM_CMD_PRES_OUT,
CTLADM_CMD_INQ_VPD_DEVID,
CTLADM_CMD_RTPG,
CTLADM_CMD_MODIFY
CTLADM_CMD_MODIFY,
CTLADM_CMD_ISLIST,
CTLADM_CMD_ISLOGOUT,
CTLADM_CMD_ISTERMINATE
} ctladm_cmdfunction;
typedef enum {
@ -180,6 +183,9 @@ static struct ctladm_opts option_table[] = {
{"help", CTLADM_CMD_HELP, CTLADM_ARG_NONE, NULL},
{"inject", CTLADM_CMD_ERR_INJECT, CTLADM_ARG_NEED_TL, "cd:i:p:r:s:"},
{"inquiry", CTLADM_CMD_INQUIRY, CTLADM_ARG_NEED_TL, NULL},
{"islist", CTLADM_CMD_ISLIST, CTLADM_ARG_NONE, "vx"},
{"islogout", CTLADM_CMD_ISLOGOUT, CTLADM_ARG_NONE, "ah:c:i:"},
{"isterminate", CTLADM_CMD_ISTERMINATE, CTLADM_ARG_NONE, "ah:c:i:"},
{"lunlist", CTLADM_CMD_LUNLIST, CTLADM_ARG_NONE, NULL},
{"modesense", CTLADM_CMD_MODESENSE, CTLADM_ARG_NEED_TL, "P:S:dlm:c:"},
{"modify", CTLADM_CMD_MODIFY, CTLADM_ARG_NONE, "b:l:s:"},
@ -489,6 +495,9 @@ cctl_port_dump(int fd, int quiet, int xml, int32_t targ_port,
case CTL_PORT_ISC:
type = "ISC";
break;
case CTL_PORT_ISCSI:
type = "ISCSI";
break;
default:
type = "UNKNOWN";
break;
@ -578,6 +587,7 @@ static struct ctladm_opts cctl_fe_table[] = {
{"fc", CTL_PORT_FC, CTLADM_ARG_NONE, NULL},
{"scsi", CTL_PORT_SCSI, CTLADM_ARG_NONE, NULL},
{"internal", CTL_PORT_INTERNAL, CTLADM_ARG_NONE, NULL},
{"iscsi", CTL_PORT_ISCSI, CTLADM_ARG_NONE, NULL},
{"all", CTL_PORT_ALL, CTLADM_ARG_NONE, NULL},
{NULL, 0, 0, NULL}
};
@ -3399,6 +3409,403 @@ cctl_modify_lun(int fd, int argc, char **argv, char *combinedopt)
return (retval);
}
struct cctl_islist_conn {
int connection_id;
char *initiator;
char *initiator_addr;
char *initiator_alias;
char *target;
char *target_alias;
char *header_digest;
char *data_digest;
char *max_data_segment_length;;
int immediate_data;
int iser;
STAILQ_ENTRY(cctl_islist_conn) links;
};
struct cctl_islist_data {
int num_conns;
STAILQ_HEAD(,cctl_islist_conn) conn_list;
struct cctl_islist_conn *cur_conn;
int level;
struct sbuf *cur_sb[32];
};
static void
cctl_islist_start_element(void *user_data, const char *name, const char **attr)
{
int i;
struct cctl_islist_data *islist;
struct cctl_islist_conn *cur_conn;
islist = (struct cctl_islist_data *)user_data;
cur_conn = islist->cur_conn;
islist->level++;
if ((u_int)islist->level > (sizeof(islist->cur_sb) /
sizeof(islist->cur_sb[0])))
errx(1, "%s: too many nesting levels, %zd max", __func__,
sizeof(islist->cur_sb) / sizeof(islist->cur_sb[0]));
islist->cur_sb[islist->level] = sbuf_new_auto();
if (islist->cur_sb[islist->level] == NULL)
err(1, "%s: Unable to allocate sbuf", __func__);
if (strcmp(name, "connection") == 0) {
if (cur_conn != NULL)
errx(1, "%s: improper connection element nesting",
__func__);
cur_conn = calloc(1, sizeof(*cur_conn));
if (cur_conn == NULL)
err(1, "%s: cannot allocate %zd bytes", __func__,
sizeof(*cur_conn));
islist->num_conns++;
islist->cur_conn = cur_conn;
STAILQ_INSERT_TAIL(&islist->conn_list, cur_conn, links);
for (i = 0; attr[i] != NULL; i += 2) {
if (strcmp(attr[i], "id") == 0) {
cur_conn->connection_id =
strtoull(attr[i+1], NULL, 0);
} else {
errx(1,
"%s: invalid connection attribute %s = %s",
__func__, attr[i], attr[i+1]);
}
}
}
}
static void
cctl_islist_end_element(void *user_data, const char *name)
{
struct cctl_islist_data *islist;
struct cctl_islist_conn *cur_conn;
char *str;
islist = (struct cctl_islist_data *)user_data;
cur_conn = islist->cur_conn;
if ((cur_conn == NULL)
&& (strcmp(name, "ctlislist") != 0))
errx(1, "%s: cur_conn == NULL! (name = %s)", __func__, name);
if (islist->cur_sb[islist->level] == NULL)
errx(1, "%s: no valid sbuf at level %d (name %s)", __func__,
islist->level, name);
sbuf_finish(islist->cur_sb[islist->level]);
str = strdup(sbuf_data(islist->cur_sb[islist->level]));
if (str == NULL)
err(1, "%s can't allocate %zd bytes for string", __func__,
sbuf_len(islist->cur_sb[islist->level]));
sbuf_delete(islist->cur_sb[islist->level]);
islist->cur_sb[islist->level] = NULL;
islist->level--;
if (strcmp(name, "initiator") == 0) {
cur_conn->initiator = str;
str = NULL;
} else if (strcmp(name, "initiator_addr") == 0) {
cur_conn->initiator_addr = str;
str = NULL;
} else if (strcmp(name, "initiator_alias") == 0) {
cur_conn->initiator_alias = str;
str = NULL;
} else if (strcmp(name, "target") == 0) {
cur_conn->target = str;
str = NULL;
} else if (strcmp(name, "target_alias") == 0) {
cur_conn->target_alias = str;
str = NULL;
} else if (strcmp(name, "header_digest") == 0) {
cur_conn->header_digest = str;
str = NULL;
} else if (strcmp(name, "data_digest") == 0) {
cur_conn->data_digest = str;
str = NULL;
} else if (strcmp(name, "max_data_segment_length") == 0) {
cur_conn->max_data_segment_length = str;
str = NULL;
} else if (strcmp(name, "immediate_data") == 0) {
cur_conn->immediate_data = atoi(str);
} else if (strcmp(name, "iser") == 0) {
cur_conn->iser = atoi(str);
} else if (strcmp(name, "connection") == 0) {
islist->cur_conn = NULL;
} else if (strcmp(name, "ctlislist") == 0) {
} else
errx(1, "unknown element %s", name);
free(str);
}
static void
cctl_islist_char_handler(void *user_data, const XML_Char *str, int len)
{
struct cctl_islist_data *islist;
islist = (struct cctl_islist_data *)user_data;
sbuf_bcat(islist->cur_sb[islist->level], str, len);
}
static int
cctl_islist(int fd, int argc, char **argv, char *combinedopt)
{
struct ctl_iscsi req;
struct cctl_islist_data islist;
struct cctl_islist_conn *conn;
XML_Parser parser;
char *conn_str;
int conn_len;
int dump_xml = 0;
int c, retval, verbose = 0;
retval = 0;
conn_len = 4096;
bzero(&islist, sizeof(islist));
STAILQ_INIT(&islist.conn_list);
while ((c = getopt(argc, argv, combinedopt)) != -1) {
switch (c) {
case 'v':
verbose = 1;
break;
case 'x':
dump_xml = 1;
break;
default:
break;
}
}
retry:
conn_str = malloc(conn_len);
bzero(&req, sizeof(req));
req.type = CTL_ISCSI_LIST;
req.data.list.alloc_len = conn_len;
req.data.list.conn_xml = conn_str;
if (ioctl(fd, CTL_ISCSI, &req) == -1) {
warn("%s: error issuing CTL_ISCSI ioctl", __func__);
retval = 1;
goto bailout;
}
if (req.status == CTL_ISCSI_ERROR) {
warnx("%s: error returned from CTL_ISCSI ioctl:\n%s",
__func__, req.error_str);
} else if (req.status == CTL_ISCSI_LIST_NEED_MORE_SPACE) {
conn_len = conn_len << 1;
goto retry;
}
if (dump_xml != 0) {
printf("%s", conn_str);
goto bailout;
}
parser = XML_ParserCreate(NULL);
if (parser == NULL) {
warn("%s: Unable to create XML parser", __func__);
retval = 1;
goto bailout;
}
XML_SetUserData(parser, &islist);
XML_SetElementHandler(parser, cctl_islist_start_element,
cctl_islist_end_element);
XML_SetCharacterDataHandler(parser, cctl_islist_char_handler);
retval = XML_Parse(parser, conn_str, strlen(conn_str), 1);
XML_ParserFree(parser);
if (retval != 1) {
retval = 1;
goto bailout;
}
if (verbose != 0) {
STAILQ_FOREACH(conn, &islist.conn_list, links) {
printf("Session ID: %d\n", conn->connection_id);
printf("Initiator name: %s\n", conn->initiator);
printf("Initiator addr: %s\n", conn->initiator_addr);
printf("Initiator alias: %s\n", conn->initiator_alias);
printf("Target name: %s\n", conn->target);
printf("Target alias: %s\n", conn->target_alias);
printf("Header digest: %s\n", conn->header_digest);
printf("Data digest: %s\n", conn->data_digest);
printf("DataSegmentLen: %s\n", conn->max_data_segment_length);
printf("ImmediateData: %s\n", conn->immediate_data ? "Yes" : "No");
printf("iSER (RDMA): %s\n", conn->iser ? "Yes" : "No");
printf("\n");
}
} else {
printf("%4s %-16s %-36s %-36s\n", "ID", "Address", "Initiator name",
"Target name");
STAILQ_FOREACH(conn, &islist.conn_list, links) {
printf("%4u %-16s %-36s %-36s\n",
conn->connection_id, conn->initiator_addr, conn->initiator,
conn->target);
}
}
bailout:
free(conn_str);
return (retval);
}
static int
cctl_islogout(int fd, int argc, char **argv, char *combinedopt)
{
struct ctl_iscsi req;
int retval = 0, c;
int all = 0, connection_id = -1, nargs = 0;
char *initiator_name = NULL, *initiator_addr = NULL;
while ((c = getopt(argc, argv, combinedopt)) != -1) {
switch (c) {
case 'a':
all = 1;
nargs++;
break;
case 'h':
initiator_addr = strdup(optarg);
if (initiator_addr == NULL)
err(1, "%s: strdup", __func__);
nargs++;
break;
case 'c':
connection_id = strtoul(optarg, NULL, 0);
nargs++;
break;
case 'i':
initiator_name = strdup(optarg);
if (initiator_name == NULL)
err(1, "%s: strdup", __func__);
nargs++;
break;
default:
break;
}
}
if (nargs == 0)
errx(1, "%s: either -a, -h, -c, or -i must be specified",
__func__);
if (nargs > 1)
errx(1, "%s: only one of -a, -h, -c, or -i may be specified",
__func__);
bzero(&req, sizeof(req));
req.type = CTL_ISCSI_LOGOUT;
req.data.logout.connection_id = connection_id;
if (initiator_addr != NULL)
strlcpy(req.data.logout.initiator_addr,
initiator_addr, sizeof(req.data.logout.initiator_addr));
if (initiator_name != NULL)
strlcpy(req.data.logout.initiator_name,
initiator_name, sizeof(req.data.logout.initiator_name));
if (all != 0)
req.data.logout.all = 1;
if (ioctl(fd, CTL_ISCSI, &req) == -1) {
warn("%s: error issuing CTL_ISCSI ioctl", __func__);
retval = 1;
goto bailout;
}
if (req.status != CTL_ISCSI_OK) {
warnx("%s: error returned from CTL iSCSI logout request:\n%s",
__func__, req.error_str);
retval = 1;
goto bailout;
}
printf("iSCSI logout requests submitted\n");
bailout:
return (retval);
}
static int
cctl_isterminate(int fd, int argc, char **argv, char *combinedopt)
{
struct ctl_iscsi req;
int retval = 0, c;
int all = 0, connection_id = -1, nargs = 0;
char *initiator_name = NULL, *initiator_addr = NULL;
while ((c = getopt(argc, argv, combinedopt)) != -1) {
switch (c) {
case 'a':
all = 1;
nargs++;
break;
case 'h':
initiator_addr = strdup(optarg);
if (initiator_addr == NULL)
err(1, "%s: strdup", __func__);
nargs++;
break;
case 'c':
connection_id = strtoul(optarg, NULL, 0);
nargs++;
break;
case 'i':
initiator_name = strdup(optarg);
if (initiator_name == NULL)
err(1, "%s: strdup", __func__);
nargs++;
break;
default:
break;
}
}
if (nargs == 0)
errx(1, "%s: either -a, -h, -c, or -i must be specified",
__func__);
if (nargs > 1)
errx(1, "%s: only one of -a, -h, -c, or -i may be specified",
__func__);
bzero(&req, sizeof(req));
req.type = CTL_ISCSI_TERMINATE;
req.data.terminate.connection_id = connection_id;
if (initiator_addr != NULL)
strlcpy(req.data.terminate.initiator_addr,
initiator_addr, sizeof(req.data.terminate.initiator_addr));
if (initiator_name != NULL)
strlcpy(req.data.terminate.initiator_name,
initiator_name, sizeof(req.data.terminate.initiator_name));
if (all != 0)
req.data.terminate.all = 1;
if (ioctl(fd, CTL_ISCSI, &req) == -1) {
warn("%s: error issuing CTL_ISCSI ioctl", __func__);
retval = 1;
goto bailout;
}
if (req.status != CTL_ISCSI_OK) {
warnx("%s: error returned from CTL iSCSI connection "
"termination request:\n%s", __func__, req.error_str);
retval = 1;
goto bailout;
}
printf("iSCSI connections terminated\n");
bailout:
return (retval);
}
/*
* Name/value pair used for per-LUN attributes.
@ -3713,6 +4120,9 @@ usage(int error)
" [-s len fmt [args]] [-c] [-d delete_id]\n"
" ctladm port <-l | -o <on|off> | [-w wwnn][-W wwpn]>\n"
" [-p targ_port] [-t port_type] [-q] [-x]\n"
" ctladm islist [-v | -x]\n"
" ctladm islogout <-A | -a addr | -c connection-id | -n name>\n"
" ctladm isterminate <-A | -a addr | -c connection-id | -n name>\n"
" ctladm dumpooa\n"
" ctladm dumpstructs\n"
" ctladm help\n"
@ -4093,6 +4503,15 @@ main(int argc, char **argv)
case CTLADM_CMD_MODIFY:
retval = cctl_modify_lun(fd, argc, argv, combinedopt);
break;
case CTLADM_CMD_ISLIST:
retval = cctl_islist(fd, argc, argv, combinedopt);
break;
case CTLADM_CMD_ISLOGOUT:
retval = cctl_islogout(fd, argc, argv, combinedopt);
break;
case CTLADM_CMD_ISTERMINATE:
retval = cctl_isterminate(fd, argc, argv, combinedopt);
break;
case CTLADM_CMD_HELP:
default:
usage(retval);

21
usr.sbin/ctld/Makefile Normal file
View File

@ -0,0 +1,21 @@
# $FreeBSD$
PROG= ctld
SRCS= ctld.c discovery.c kernel.c keys.c log.c login.c parse.y pdu.c token.l y.tab.h
CFLAGS+= -I${.CURDIR}
CFLAGS+= -I${.CURDIR}/../../sys
CFLAGS+= -I${.CURDIR}/../../sys/cam/ctl
CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi
#CFLAGS+= -DICL_KERNEL_PROXY
MAN= ctld.8 ctl.conf.5
DPADD= ${LIBCAM} ${LIBSBUF} ${LIBBSDXML} ${LIBUTIL}
LDADD= -lbsdxml -lcam -lcrypto -lfl -lsbuf -lssl -lutil
YFLAGS+= -v
CLEANFILES= y.tab.c y.tab.h y.output
WARNS= 6
NO_WMISSING_VARIABLE_DECLARATIONS=
.include <bsd.prog.mk>

244
usr.sbin/ctld/ctl.conf.5 Normal file
View File

@ -0,0 +1,244 @@
.\" Copyright (c) 2012 The FreeBSD Foundation
.\" All rights reserved.
.\"
.\" This software was developed by Edward Tomasz Napierala under sponsorship
.\" from the FreeBSD Foundation.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd April 24, 2013
.Dt CTL.CONF 5
.Os
.Sh NAME
.Nm ctl.conf
.Nd CAM Target Layer / iSCSI target daemon configuration file
.Sh DESCRIPTION
The
.Nm
configuration file is used by the
.Xr ctld 8
daemon.
Lines starting with
.Ql #
and empty lines are interpreted as comments.
The general syntax of the
.Nm
file is:
.Bd -literal -offset indent
pidfile <path>
auth-group <name> {
chap <user> <secret>
...
}
portal-group <name> {
listen <address>
listen-iser <address>
discovery-auth-group <name>
...
}
target <name> {
auth-group <name>
portal-group <name>
lun <number> {
path <path>
}
...
}
.Ed
.Ss global level
The following statements are available at the global level:
.Bl -tag -width indent
.It Ic auth-group Aq Ar name
Opens an auth-group section, defining an authentication group,
which can then be assigned to any number of targets.
.It Ic debug Aq Ar level
Specifies debug level.
The default is 0.
.It Ic maxproc Aq Ar number
Specifies limit for concurrently running child processes handling
incoming connections.
The default is 30.
Setting it to 0 disables the limit.
.It Ic pidfile Aq Ar path
Specifies path to pidfile.
The default is
.Pa /var/run/ctld.pid .
.It Ic portal-group Aq Ar name
Opens a portal-group section, defining a portal group,
which can then be assigned to any number of targets.
.It Ic target Aq Ar name
Opens a target configuration section.
.It Ic timeout Aq Ar seconds
Specifies timeout for login session, after which the connection
will be forcibly terminated.
The default is 60.
Setting it to 0 disables the timeout.
.El
.Ss auth-grup level
The following statements are available at the auth-group level:
.Bl -tag -width indent
.It Ic chap Ao Ar user Ac Aq Ar secret
Specifies CHAP authentication credentials.
.It Ic chap-mutual Ao Ar user Ac Ao Ar secret Ac Ao Ar mutualuser Ac Aq Ar mutualsecret
Specifies mutual CHAP authentication credentials.
Note that for any auth-group, configuration may contain either chap,
or chap-mutual entries; it's an error to mix them.
.El
.Ss portal-group level
The following statements are available at the portal-group level:
.Bl -tag -width indent
.It Ic discovery-auth-group Aq Ar name
Assigns previously defined authentication group to that portal group,
to be used for target discovery.
By default, the discovery will be denied.
A special auth-group, "no-authentication", may be used to allow for discovery
without authentication.
.It Ic listen Aq Ar address
Specifies IPv4 or IPv6 address and port to listen on for incoming connections.
.It Ic listen-iser Aq Ar address
Specifies IPv4 or IPv6 address and port to listen on for incoming connections
using iSER (iSCSI over RDMA) protocol.
.El
.Ss target level:
The following statements are available at the target level:
.Bl -tag -width indent
.It Ic alias Aq Ar text
Assigns human-readable description to that target.
There is no default.
.It Ic auth-group Aq Ar name
Assigns previously defined authentication group to that target.
There is no default; every target must use either auth-group,
or chap, or chap-mutual statements.
A special auth-group, "no-authentication", may be used to permit access
without authentication.
.It Ic chap Ao Ar user Ac Aq Ar secret
Specifies CHAP authentication credentials.
Note that targets must use either auth-group, or chap,
or chap-mutual clauses; it's a configuration error to mix them in one target.
.It Ic chap-mutual Ao Ar user Ac Ao Ar secret Ac Ao Ar mutualuser Ac Aq Ar mutualsecret
Specifies mutual CHAP authentication credentials.
Note that targets must use either auth-group, chap,
chap-mutual clauses; it's a configuration error to mix them in one target.
.It Ic portal-group Aq Ar name
Assigns previously defined portal group to that target.
Default portal group is "default", which makes the target available
on TCP port 3260 on all configured IPv4 and IPv6 addresses.
.It Ic lun Aq Ar number
Opens a lun configuration section, defining LUN exported by a target.
.El
.Ss lun level
The following statements are available at the lun level:
.Bl -tag -width indent
.It Ic backend Ao Ar block | Ar ramdisk Ac
Specifies the CTL backend to use for a given LUN.
Valid choices are
.Dq block
and
.Dq ramdisk ;
block is used for LUNs backed
by files in the filesystem; ramdisk is a bitsink device, used mostly for
testing.
The default backend is block.
.It Ic blocksize Aq Ar size
Specifies blocksize visible to the initiator.
The default blocksize is 512.
.It Ic device-id Aq Ar string
Specifies SCSI Device Identification string presented to the initiator.
.It Ic option Ao Ar name Ac Aq Ar value
Specifies CTL-specific options passed to the kernel.
.It Ic path Aq Ar path
Specifies path to file used to back the LUN.
.It Ic serial Aq Ar string
Specifies SCSI serial number presented to the initiator.
.It Ic size Aq Ar size
Specifies LUN size, in bytes.
.El
.Sh FILES
.Bl -tag -width ".Pa /etc/ctl.conf" -compact
.It Pa /etc/ctl.conf
The default location of the
.Xr ctld 8
configuration file.
.El
.Sh EXAMPLES
.Bd -literal
pidfile /var/run/ctld.pid
auth-group example2 {
chap-mutual "user" "secret" "mutualuser" "mutualsecret"
chap-mutual "user2" "secret2" "mutualuser" "mutualsecret"
}
portal-group example2 {
discovery-auth-group no-authentication
listen 127.0.0.1
listen 0.0.0.0:3261
listen [::]:3261
listen [fe80::be:ef]
}
target iqn.2012-06.com.example:target0 {
alias "Testing target"
auth-group no-authentication
lun 0 {
path /dev/zvol/example_0
blocksize 4096
size 4G
}
}
target iqn.2012-06.com.example:target3 {
chap chapuser chapsecret
lun 0 {
path /dev/zvol/example_3
}
}
target iqn.2012-06.com.example:target2 {
auth-group example2
portal-group example2
lun 0 {
path /dev/zvol/example2_0
}
lun 1 {
path /dev/zvol/example2_1
option foo bar
}
}
.Ed
.Sh SEE ALSO
.Xr ctl 4 ,
.Xr ctladm 8 ,
.Xr ctld 8
.Sh AUTHORS
The
.Nm
configuration file functionality for
.Xr ctld 8
was developed by
.An Edward Tomasz Napierala Aq trasz@FreeBSD.org
under sponsorship from the FreeBSD Foundation.

114
usr.sbin/ctld/ctld.8 Normal file
View File

@ -0,0 +1,114 @@
.\" Copyright (c) 2012 The FreeBSD Foundation
.\" All rights reserved.
.\"
.\" This software was developed by Edward Tomasz Napierala under sponsorship
.\" from the FreeBSD Foundation.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd September 20, 2012
.Dt CTLD 8
.Os
.Sh NAME
.Nm ctld
.Nd CAM Target Layer / iSCSI target daemon
.Sh SYNOPSIS
.Nm
.Op Fl d
.Op Fl f Ar config-file
.Sh DESCRIPTION
The
.Nm
daemon is responsible for managing the CAM Target Layer configuration,
accepting incoming iSCSI connections, performing authentication and
passing connections to the kernel part of the native iSCSI target.
.Pp
.Pp
Upon startup, the
.Nm
daemon parses the configuration file and exits, if it encounters any errors.
Then it compares the configuration with the kernel list of LUNs managed
by previously running
.Nm
instances, removes LUNs no longer existing in the configuration file,
and creates new LUNs as neccessary.
After that it listens for the incoming iSCSI connections, performs
authentication, and, if successful, passes the connections to the kernel part
of CTL iSCSI target, which handles it from that point.
.Pp
When it receives a SIGHUP signal, the
.Nm
reloads its configuration and applies the changes to the kernel.
Changes are applied in a way that avoids unneccessary disruptions;
for example removing one LUN does not affect other LUNs.
.Pp
When exiting gracefully, the
.Nm
daemon removes LUNs it managed and forcibly disconnects all the clients.
Otherwise - e.g. when killed with SIGKILL - LUNs stay configured
and clients remain connected.
.Pp
To perform administrative actions that apply to already connected
sessions, such as forcing termination, use
.Xr ctladm 8 .
.Pp
The following options are available:
.Bl -tag -width ".Fl P Ar pidfile"
.It Fl f Ar config-file
Specifies the name of the configuration file.
The default is
.Pa /etc/ctl.conf .
.It Fl d
Debug mode.
The server sends verbose debug output to standard error, and does not
put itself in the background.
The server will also not fork and will exit after processing one connection.
This option is only intended for debugging the target.
.El
.Sh FILES
.Bl -tag -width ".Pa /var/run/ctld.pid" -compact
.It Pa /etc/ctl.conf
The configuration file for
.Nm .
The file format and configuration options are described in
.Xr ctl.conf 5 .
.It Pa /var/run/ctld.pid
The default location of the
.Nm
PID file.
.El
.Sh EXIT STATUS
The
.Nm
utility exits 0 on success, and >0 if an error occurs.
.Sh SEE ALSO
.Xr ctl 4 ,
.Xr ctl.conf 5 ,
.Xr ctladm 8
.Sh AUTHORS
The
.Nm
was developed by
.An Edward Tomasz Napierala Aq trasz@FreeBSD.org
under sponsorship from the FreeBSD Foundation.

1715
usr.sbin/ctld/ctld.c Normal file

File diff suppressed because it is too large Load Diff

277
usr.sbin/ctld/ctld.h Normal file
View File

@ -0,0 +1,277 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef CTLD_H
#define CTLD_H
#include <sys/queue.h>
#include <stdbool.h>
#include <libutil.h>
#define DEFAULT_CONFIG_PATH "/etc/ctl.conf"
#define DEFAULT_PIDFILE "/var/run/ctld.pid"
#define DEFAULT_BLOCKSIZE 512
#define MAX_NAME_LEN 223
#define MAX_DATA_SEGMENT_LENGTH (128 * 1024)
#define MAX_BURST_LENGTH 16776192
struct auth {
TAILQ_ENTRY(auth) a_next;
struct auth_group *a_auth_group;
char *a_user;
char *a_secret;
char *a_mutual_user;
char *a_mutual_secret;
};
#define AG_TYPE_UNKNOWN 0
#define AG_TYPE_NO_AUTHENTICATION 1
#define AG_TYPE_CHAP 2
#define AG_TYPE_CHAP_MUTUAL 3
struct auth_group {
TAILQ_ENTRY(auth_group) ag_next;
struct conf *ag_conf;
char *ag_name;
struct target *ag_target;
int ag_type;
TAILQ_HEAD(, auth) ag_auths;
};
struct portal {
TAILQ_ENTRY(portal) p_next;
struct portal_group *p_portal_group;
bool p_iser;
char *p_listen;
struct addrinfo *p_ai;
TAILQ_HEAD(, target) p_targets;
int p_socket;
};
struct portal_group {
TAILQ_ENTRY(portal_group) pg_next;
struct conf *pg_conf;
char *pg_name;
struct auth_group *pg_discovery_auth_group;
bool pg_unassigned;
TAILQ_HEAD(, portal) pg_portals;
uint16_t pg_tag;
};
struct lun_option {
TAILQ_ENTRY(lun_option) lo_next;
struct lun *lo_lun;
char *lo_name;
char *lo_value;
};
struct lun {
TAILQ_ENTRY(lun) l_next;
TAILQ_HEAD(, lun_option) l_options;
struct target *l_target;
int l_lun;
char *l_backend;
int l_blocksize;
char *l_device_id;
char *l_path;
char *l_serial;
int64_t l_size;
int l_ctl_lun;
};
struct target {
TAILQ_ENTRY(target) t_next;
TAILQ_HEAD(, lun) t_luns;
struct conf *t_conf;
struct auth_group *t_auth_group;
struct portal_group *t_portal_group;
char *t_iqn;
char *t_alias;
};
struct conf {
char *conf_pidfile_path;
TAILQ_HEAD(, target) conf_targets;
TAILQ_HEAD(, auth_group) conf_auth_groups;
TAILQ_HEAD(, portal_group) conf_portal_groups;
int conf_debug;
int conf_timeout;
int conf_maxproc;
uint16_t conf_last_portal_group_tag;
struct pidfh *conf_pidfh;
};
#define CONN_SESSION_TYPE_NONE 0
#define CONN_SESSION_TYPE_DISCOVERY 1
#define CONN_SESSION_TYPE_NORMAL 2
#define CONN_DIGEST_NONE 0
#define CONN_DIGEST_CRC32C 1
struct connection {
struct portal *conn_portal;
struct target *conn_target;
int conn_socket;
int conn_session_type;
char *conn_initiator_name;
char *conn_initiator_addr;
char *conn_initiator_alias;
uint32_t conn_cmdsn;
uint32_t conn_statsn;
size_t conn_max_data_segment_length;
size_t conn_max_burst_length;
int conn_immediate_data;
int conn_header_digest;
int conn_data_digest;
};
struct pdu {
struct connection *pdu_connection;
struct iscsi_bhs *pdu_bhs;
char *pdu_data;
size_t pdu_data_len;
};
#define KEYS_MAX 1024
struct keys {
char *keys_names[KEYS_MAX];
char *keys_values[KEYS_MAX];
char *keys_data;
size_t keys_data_len;
};
struct conf *conf_new(void);
struct conf *conf_new_from_file(const char *path);
struct conf *conf_new_from_kernel(void);
void conf_delete(struct conf *conf);
int conf_verify(struct conf *conf);
struct auth_group *auth_group_new(struct conf *conf, const char *name);
void auth_group_delete(struct auth_group *ag);
struct auth_group *auth_group_find(struct conf *conf, const char *name);
const struct auth *auth_new_chap(struct auth_group *ag,
const char *user, const char *secret);
const struct auth *auth_new_chap_mutual(struct auth_group *ag,
const char *user, const char *secret,
const char *user2, const char *secret2);
const struct auth *auth_find(struct auth_group *ag,
const char *user);
struct portal_group *portal_group_new(struct conf *conf, const char *name);
void portal_group_delete(struct portal_group *pg);
struct portal_group *portal_group_find(struct conf *conf, const char *name);
int portal_group_add_listen(struct portal_group *pg,
const char *listen, bool iser);
struct target *target_new(struct conf *conf, const char *iqn);
void target_delete(struct target *target);
struct target *target_find(struct conf *conf,
const char *iqn);
struct lun *lun_new(struct target *target, int lun_id);
void lun_delete(struct lun *lun);
struct lun *lun_find(struct target *target, int lun_id);
void lun_set_backend(struct lun *lun, const char *value);
void lun_set_blocksize(struct lun *lun, size_t value);
void lun_set_device_id(struct lun *lun, const char *value);
void lun_set_path(struct lun *lun, const char *value);
void lun_set_serial(struct lun *lun, const char *value);
void lun_set_size(struct lun *lun, size_t value);
void lun_set_ctl_lun(struct lun *lun, uint32_t value);
struct lun_option *lun_option_new(struct lun *lun,
const char *name, const char *value);
void lun_option_delete(struct lun_option *clo);
struct lun_option *lun_option_find(struct lun *lun, const char *name);
void lun_option_set(struct lun_option *clo,
const char *value);
void kernel_init(void);
int kernel_lun_add(struct lun *lun);
int kernel_lun_resize(struct lun *lun);
int kernel_lun_remove(struct lun *lun);
void kernel_handoff(struct connection *conn);
int kernel_port_on(void);
int kernel_port_off(void);
void kernel_capsicate(void);
/*
* ICL_KERNEL_PROXY
*/
void kernel_listen(struct addrinfo *ai, bool iser);
int kernel_accept(void);
void kernel_send(struct pdu *pdu);
void kernel_receive(struct pdu *pdu);
struct keys *keys_new(void);
void keys_delete(struct keys *keys);
void keys_load(struct keys *keys, const struct pdu *pdu);
void keys_save(struct keys *keys, struct pdu *pdu);
const char *keys_find(struct keys *keys, const char *name);
int keys_find_int(struct keys *keys, const char *name);
void keys_add(struct keys *keys,
const char *name, const char *value);
void keys_add_int(struct keys *keys,
const char *name, int value);
struct pdu *pdu_new(struct connection *conn);
struct pdu *pdu_new_response(struct pdu *request);
void pdu_delete(struct pdu *pdu);
void pdu_receive(struct pdu *request);
void pdu_send(struct pdu *response);
void login(struct connection *conn);
void discovery(struct connection *conn);
void log_init(int level);
void log_set_peer_name(const char *name);
void log_set_peer_addr(const char *addr);
void log_err(int, const char *, ...)
__dead2 __printf0like(2, 3);
void log_errx(int, const char *, ...)
__dead2 __printf0like(2, 3);
void log_warn(const char *, ...) __printf0like(1, 2);
void log_warnx(const char *, ...) __printflike(1, 2);
void log_debugx(const char *, ...) __printf0like(1, 2);
char *checked_strdup(const char *);
bool valid_iscsi_name(const char *name);
bool timed_out(void);
#endif /* !CTLD_H */

221
usr.sbin/ctld/discovery.c Normal file
View File

@ -0,0 +1,221 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include "ctld.h"
#include "iscsi_proto.h"
static struct pdu *
text_receive(struct connection *conn)
{
struct pdu *request;
struct iscsi_bhs_text_request *bhstr;
request = pdu_new(conn);
pdu_receive(request);
if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
ISCSI_BHS_OPCODE_TEXT_REQUEST)
log_errx(1, "protocol error: received invalid opcode 0x%x",
request->pdu_bhs->bhs_opcode);
bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
#if 0
if ((bhstr->bhstr_flags & ISCSI_BHSTR_FLAGS_FINAL) == 0)
log_errx(1, "received Text PDU without the \"F\" flag");
#endif
/*
* XXX: Implement the C flag some day.
*/
if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0)
log_errx(1, "received Text PDU with unsupported \"C\" flag");
if (request->pdu_data_len == 0)
log_errx(1, "received Text PDU with empty data segment");
if (ntohl(bhstr->bhstr_cmdsn) < conn->conn_cmdsn) {
log_errx(1, "received Text PDU with decreasing CmdSN: "
"was %d, is %d", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn));
}
if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) {
log_errx(1, "received Text PDU with wrong StatSN: "
"is %d, should be %d", ntohl(bhstr->bhstr_expstatsn),
conn->conn_statsn);
}
conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn);
return (request);
}
static struct pdu *
text_new_response(struct pdu *request)
{
struct pdu *response;
struct connection *conn;
struct iscsi_bhs_text_request *bhstr;
struct iscsi_bhs_text_response *bhstr2;
bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
conn = request->pdu_connection;
response = pdu_new_response(request);
bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs;
bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE;
bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL;
bhstr2->bhstr_lun = bhstr->bhstr_lun;
bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag;
bhstr2->bhstr_target_transfer_tag = bhstr->bhstr_target_transfer_tag;
bhstr2->bhstr_statsn = htonl(conn->conn_statsn++);
bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn);
bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn);
return (response);
}
static struct pdu *
logout_receive(struct connection *conn)
{
struct pdu *request;
struct iscsi_bhs_logout_request *bhslr;
request = pdu_new(conn);
pdu_receive(request);
if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
ISCSI_BHS_OPCODE_LOGOUT_REQUEST)
log_errx(1, "protocol error: received invalid opcode 0x%x",
request->pdu_bhs->bhs_opcode);
bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs;
if ((bhslr->bhslr_reason & 0x7f) != BHSLR_REASON_CLOSE_SESSION)
log_debugx("received Logout PDU with invalid reason 0x%x; "
"continuing anyway", bhslr->bhslr_reason & 0x7f);
if (ntohl(bhslr->bhslr_cmdsn) < conn->conn_cmdsn) {
log_errx(1, "received Logout PDU with decreasing CmdSN: "
"was %d, is %d", conn->conn_cmdsn,
ntohl(bhslr->bhslr_cmdsn));
}
if (ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) {
log_errx(1, "received Logout PDU with wrong StatSN: "
"is %d, should be %d", ntohl(bhslr->bhslr_expstatsn),
conn->conn_statsn);
}
conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn);
return (request);
}
static struct pdu *
logout_new_response(struct pdu *request)
{
struct pdu *response;
struct connection *conn;
struct iscsi_bhs_logout_request *bhslr;
struct iscsi_bhs_logout_response *bhslr2;
bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs;
conn = request->pdu_connection;
response = pdu_new_response(request);
bhslr2 = (struct iscsi_bhs_logout_response *)response->pdu_bhs;
bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE;
bhslr2->bhslr_flags = 0x80;
bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY;
bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag;
bhslr2->bhslr_statsn = htonl(conn->conn_statsn++);
bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn);
bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn);
return (response);
}
void
discovery(struct connection *conn)
{
struct pdu *request, *response;
struct keys *request_keys, *response_keys;
struct target *targ;
const char *send_targets;
log_debugx("beginning discovery session; waiting for Text PDU");
request = text_receive(conn);
request_keys = keys_new();
keys_load(request_keys, request);
send_targets = keys_find(request_keys, "SendTargets");
if (send_targets == NULL)
log_errx(1, "received Text PDU without SendTargets");
response = text_new_response(request);
response_keys = keys_new();
if (strcmp(send_targets, "All") == 0) {
TAILQ_FOREACH(targ,
&conn->conn_portal->p_portal_group->pg_conf->conf_targets,
t_next) {
if (targ->t_portal_group !=
conn->conn_portal->p_portal_group) {
log_debugx("not returning target \"%s\"; "
"belongs to a different portal group",
targ->t_iqn);
continue;
}
keys_add(response_keys, "TargetName", targ->t_iqn);
}
} else {
targ = target_find(conn->conn_portal->p_portal_group->pg_conf,
send_targets);
if (targ == NULL) {
log_debugx("initiator requested information on unknown "
"target \"%s\"; returning nothing", send_targets);
} else {
keys_add(response_keys, "TargetName", targ->t_iqn);
}
}
keys_save(response_keys, response);
pdu_send(response);
pdu_delete(response);
keys_delete(response_keys);
pdu_delete(request);
keys_delete(request_keys);
log_debugx("done sending targets; waiting for Logout PDU");
request = logout_receive(conn);
response = logout_new_response(request);
pdu_send(response);
pdu_delete(response);
pdu_delete(request);
log_debugx("discovery session done");
}

782
usr.sbin/ctld/kernel.c Normal file
View File

@ -0,0 +1,782 @@
/*-
* Copyright (c) 2003, 2004 Silicon Graphics International Corp.
* Copyright (c) 1997-2007 Kenneth D. Merry
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Edward Tomasz Napierala
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification.
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
* substantially similar to the "NO WARRANTY" disclaimer below
* ("Disclaimer") and any redistribution must be conditioned upon
* including a substantially similar Disclaimer requirement for further
* binary redistribution.
*
* NO WARRANTY
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* $FreeBSD$
*/
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/linker.h>
#include <sys/queue.h>
#include <sys/callout.h>
#include <sys/sbuf.h>
#include <sys/capability.h>
#include <assert.h>
#include <bsdxml.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <cam/scsi/scsi_all.h>
#include <cam/scsi/scsi_message.h>
#include <cam/ctl/ctl.h>
#include <cam/ctl/ctl_io.h>
#include <cam/ctl/ctl_frontend_internal.h>
#include <cam/ctl/ctl_backend.h>
#include <cam/ctl/ctl_ioctl.h>
#include <cam/ctl/ctl_backend_block.h>
#include <cam/ctl/ctl_util.h>
#include <cam/ctl/ctl_scsi_all.h>
#ifdef ICL_KERNEL_PROXY
#include <netdb.h>
#endif
#include "ctld.h"
static int ctl_fd = 0;
void
kernel_init(void)
{
int retval, saved_errno;
ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR);
if (ctl_fd < 0) {
saved_errno = errno;
retval = kldload("ctl");
if (retval != -1)
ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR);
else
errno = saved_errno;
}
if (ctl_fd < 0)
log_err(1, "failed to open %s", CTL_DEFAULT_DEV);
}
/*
* Name/value pair used for per-LUN attributes.
*/
struct cctl_lun_nv {
char *name;
char *value;
STAILQ_ENTRY(cctl_lun_nv) links;
};
/*
* Backend LUN information.
*/
struct cctl_lun {
uint64_t lun_id;
char *backend_type;
uint64_t size_blocks;
uint32_t blocksize;
char *serial_number;
char *device_id;
char *cfiscsi_target;
char *cfiscsi_target_alias;
int cfiscsi_lun;
STAILQ_HEAD(,cctl_lun_nv) attr_list;
STAILQ_ENTRY(cctl_lun) links;
};
struct cctl_devlist_data {
int num_luns;
STAILQ_HEAD(,cctl_lun) lun_list;
struct cctl_lun *cur_lun;
int level;
struct sbuf *cur_sb[32];
};
static void
cctl_start_element(void *user_data, const char *name, const char **attr)
{
int i;
struct cctl_devlist_data *devlist;
struct cctl_lun *cur_lun;
devlist = (struct cctl_devlist_data *)user_data;
cur_lun = devlist->cur_lun;
devlist->level++;
if ((u_int)devlist->level > (sizeof(devlist->cur_sb) /
sizeof(devlist->cur_sb[0])))
log_errx(1, "%s: too many nesting levels, %zd max", __func__,
sizeof(devlist->cur_sb) / sizeof(devlist->cur_sb[0]));
devlist->cur_sb[devlist->level] = sbuf_new_auto();
if (devlist->cur_sb[devlist->level] == NULL)
log_err(1, "%s: unable to allocate sbuf", __func__);
if (strcmp(name, "lun") == 0) {
if (cur_lun != NULL)
log_errx(1, "%s: improper lun element nesting",
__func__);
cur_lun = calloc(1, sizeof(*cur_lun));
if (cur_lun == NULL)
log_err(1, "%s: cannot allocate %zd bytes", __func__,
sizeof(*cur_lun));
devlist->num_luns++;
devlist->cur_lun = cur_lun;
STAILQ_INIT(&cur_lun->attr_list);
STAILQ_INSERT_TAIL(&devlist->lun_list, cur_lun, links);
for (i = 0; attr[i] != NULL; i += 2) {
if (strcmp(attr[i], "id") == 0) {
cur_lun->lun_id = strtoull(attr[i+1], NULL, 0);
} else {
log_errx(1, "%s: invalid LUN attribute %s = %s",
__func__, attr[i], attr[i+1]);
}
}
}
}
static void
cctl_end_element(void *user_data, const char *name)
{
struct cctl_devlist_data *devlist;
struct cctl_lun *cur_lun;
char *str;
devlist = (struct cctl_devlist_data *)user_data;
cur_lun = devlist->cur_lun;
if ((cur_lun == NULL)
&& (strcmp(name, "ctllunlist") != 0))
log_errx(1, "%s: cur_lun == NULL! (name = %s)", __func__, name);
if (devlist->cur_sb[devlist->level] == NULL)
log_errx(1, "%s: no valid sbuf at level %d (name %s)", __func__,
devlist->level, name);
sbuf_finish(devlist->cur_sb[devlist->level]);
str = checked_strdup(sbuf_data(devlist->cur_sb[devlist->level]));
if (strlen(str) == 0) {
free(str);
str = NULL;
}
sbuf_delete(devlist->cur_sb[devlist->level]);
devlist->cur_sb[devlist->level] = NULL;
devlist->level--;
if (strcmp(name, "backend_type") == 0) {
cur_lun->backend_type = str;
str = NULL;
} else if (strcmp(name, "size") == 0) {
cur_lun->size_blocks = strtoull(str, NULL, 0);
} else if (strcmp(name, "blocksize") == 0) {
cur_lun->blocksize = strtoul(str, NULL, 0);
} else if (strcmp(name, "serial_number") == 0) {
cur_lun->serial_number = str;
str = NULL;
} else if (strcmp(name, "device_id") == 0) {
cur_lun->device_id = str;
str = NULL;
} else if (strcmp(name, "cfiscsi_target") == 0) {
cur_lun->cfiscsi_target = str;
str = NULL;
} else if (strcmp(name, "cfiscsi_target_alias") == 0) {
cur_lun->cfiscsi_target_alias = str;
str = NULL;
} else if (strcmp(name, "cfiscsi_lun") == 0) {
cur_lun->cfiscsi_lun = strtoul(str, NULL, 0);
} else if (strcmp(name, "lun") == 0) {
devlist->cur_lun = NULL;
} else if (strcmp(name, "ctllunlist") == 0) {
} else {
struct cctl_lun_nv *nv;
nv = calloc(1, sizeof(*nv));
if (nv == NULL)
log_err(1, "%s: can't allocate %zd bytes for nv pair",
__func__, sizeof(*nv));
nv->name = checked_strdup(name);
nv->value = str;
str = NULL;
STAILQ_INSERT_TAIL(&cur_lun->attr_list, nv, links);
}
free(str);
}
static void
cctl_char_handler(void *user_data, const XML_Char *str, int len)
{
struct cctl_devlist_data *devlist;
devlist = (struct cctl_devlist_data *)user_data;
sbuf_bcat(devlist->cur_sb[devlist->level], str, len);
}
struct conf *
conf_new_from_kernel(void)
{
struct conf *conf = NULL;
struct target *targ;
struct lun *cl;
struct lun_option *lo;
struct ctl_lun_list list;
struct cctl_devlist_data devlist;
struct cctl_lun *lun;
XML_Parser parser;
char *lun_str = NULL;
int lun_len;
int retval;
lun_len = 4096;
bzero(&devlist, sizeof(devlist));
STAILQ_INIT(&devlist.lun_list);
log_debugx("obtaining previously configured CTL luns from the kernel");
retry:
lun_str = realloc(lun_str, lun_len);
if (lun_str == NULL)
log_err(1, "realloc");
bzero(&list, sizeof(list));
list.alloc_len = lun_len;
list.status = CTL_LUN_LIST_NONE;
list.lun_xml = lun_str;
if (ioctl(ctl_fd, CTL_LUN_LIST, &list) == -1) {
log_warn("error issuing CTL_LUN_LIST ioctl");
free(lun_str);
return (NULL);
}
if (list.status == CTL_LUN_LIST_ERROR) {
log_warnx("error returned from CTL_LUN_LIST ioctl: %s",
list.error_str);
free(lun_str);
return (NULL);
}
if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) {
lun_len = lun_len << 1;
goto retry;
}
parser = XML_ParserCreate(NULL);
if (parser == NULL) {
log_warnx("unable to create XML parser");
free(lun_str);
return (NULL);
}
XML_SetUserData(parser, &devlist);
XML_SetElementHandler(parser, cctl_start_element, cctl_end_element);
XML_SetCharacterDataHandler(parser, cctl_char_handler);
retval = XML_Parse(parser, lun_str, strlen(lun_str), 1);
XML_ParserFree(parser);
free(lun_str);
if (retval != 1) {
log_warnx("XML_Parse failed");
return (NULL);
}
conf = conf_new();
STAILQ_FOREACH(lun, &devlist.lun_list, links) {
struct cctl_lun_nv *nv;
if (lun->cfiscsi_target == NULL) {
log_debugx("CTL lun %ju wasn't managed by ctld; "
"ignoring", (uintmax_t)lun->lun_id);
continue;
}
targ = target_find(conf, lun->cfiscsi_target);
if (targ == NULL) {
#if 0
log_debugx("found new kernel target %s for CTL lun %ld",
lun->cfiscsi_target, lun->lun_id);
#endif
targ = target_new(conf, lun->cfiscsi_target);
if (targ == NULL) {
log_warnx("target_new failed");
continue;
}
}
cl = lun_find(targ, lun->cfiscsi_lun);
if (cl != NULL) {
log_warnx("found CTL lun %ju, backing lun %d, target "
"%s, also backed by CTL lun %d; ignoring",
(uintmax_t) lun->lun_id, cl->l_lun,
cl->l_target->t_iqn, cl->l_ctl_lun);
continue;
}
log_debugx("found CTL lun %ju, backing lun %d, target %s",
(uintmax_t)lun->lun_id, lun->cfiscsi_lun, lun->cfiscsi_target);
cl = lun_new(targ, lun->cfiscsi_lun);
if (cl == NULL) {
log_warnx("lun_new failed");
continue;
}
lun_set_backend(cl, lun->backend_type);
lun_set_blocksize(cl, lun->blocksize);
lun_set_device_id(cl, lun->device_id);
lun_set_serial(cl, lun->serial_number);
lun_set_size(cl, lun->size_blocks * cl->l_blocksize);
lun_set_ctl_lun(cl, lun->lun_id);
STAILQ_FOREACH(nv, &lun->attr_list, links) {
if (strcmp(nv->name, "file") == 0 ||
strcmp(nv->name, "dev") == 0) {
lun_set_path(cl, nv->value);
continue;
}
lo = lun_option_new(cl, nv->name, nv->value);
if (lo == NULL)
log_warnx("unable to add CTL lun option %s "
"for CTL lun %ju for lun %d, target %s",
nv->name, (uintmax_t) lun->lun_id,
cl->l_lun, cl->l_target->t_iqn);
}
}
return (conf);
}
int
kernel_lun_add(struct lun *lun)
{
struct lun_option *lo;
struct ctl_lun_req req;
char *tmp;
int error, i, num_options;
bzero(&req, sizeof(req));
strlcpy(req.backend, lun->l_backend, sizeof(req.backend));
req.reqtype = CTL_LUNREQ_CREATE;
req.reqdata.create.blocksize_bytes = lun->l_blocksize;
if (lun->l_size != 0)
req.reqdata.create.lun_size_bytes = lun->l_size;
req.reqdata.create.flags |= CTL_LUN_FLAG_DEV_TYPE;
req.reqdata.create.device_type = T_DIRECT;
if (lun->l_serial != NULL) {
strlcpy(req.reqdata.create.serial_num, lun->l_serial,
sizeof(req.reqdata.create.serial_num));
req.reqdata.create.flags |= CTL_LUN_FLAG_SERIAL_NUM;
}
if (lun->l_device_id != NULL) {
strlcpy(req.reqdata.create.device_id, lun->l_device_id,
sizeof(req.reqdata.create.device_id));
req.reqdata.create.flags |= CTL_LUN_FLAG_DEVID;
}
if (lun->l_path != NULL) {
lo = lun_option_find(lun, "file");
if (lo != NULL) {
lun_option_set(lo, lun->l_path);
} else {
lo = lun_option_new(lun, "file", lun->l_path);
assert(lo != NULL);
}
}
lo = lun_option_find(lun, "cfiscsi_target");
if (lo != NULL) {
lun_option_set(lo, lun->l_target->t_iqn);
} else {
lo = lun_option_new(lun, "cfiscsi_target",
lun->l_target->t_iqn);
assert(lo != NULL);
}
if (lun->l_target->t_alias != NULL) {
lo = lun_option_find(lun, "cfiscsi_target_alias");
if (lo != NULL) {
lun_option_set(lo, lun->l_target->t_alias);
} else {
lo = lun_option_new(lun, "cfiscsi_target_alias",
lun->l_target->t_alias);
assert(lo != NULL);
}
}
asprintf(&tmp, "%d", lun->l_lun);
if (tmp == NULL)
log_errx(1, "asprintf");
lo = lun_option_find(lun, "cfiscsi_lun");
if (lo != NULL) {
lun_option_set(lo, tmp);
free(tmp);
} else {
lo = lun_option_new(lun, "cfiscsi_lun", tmp);
free(tmp);
assert(lo != NULL);
}
num_options = 0;
TAILQ_FOREACH(lo, &lun->l_options, lo_next)
num_options++;
req.num_be_args = num_options;
if (num_options > 0) {
req.be_args = malloc(num_options * sizeof(*req.be_args));
if (req.be_args == NULL) {
log_warn("error allocating %zd bytes",
num_options * sizeof(*req.be_args));
return (1);
}
i = 0;
TAILQ_FOREACH(lo, &lun->l_options, lo_next) {
/*
* +1 for the terminating '\0'
*/
req.be_args[i].namelen = strlen(lo->lo_name) + 1;
req.be_args[i].name = lo->lo_name;
req.be_args[i].vallen = strlen(lo->lo_value) + 1;
req.be_args[i].value = lo->lo_value;
req.be_args[i].flags = CTL_BEARG_ASCII | CTL_BEARG_RD;
i++;
}
assert(i == num_options);
}
error = ioctl(ctl_fd, CTL_LUN_REQ, &req);
free(req.be_args);
if (error != 0) {
log_warn("error issuing CTL_LUN_REQ ioctl");
return (1);
}
if (req.status == CTL_LUN_ERROR) {
log_warnx("error returned from LUN creation request: %s",
req.error_str);
return (1);
}
if (req.status != CTL_LUN_OK) {
log_warnx("unknown LUN creation request status %d",
req.status);
return (1);
}
lun_set_ctl_lun(lun, req.reqdata.create.req_lun_id);
return (0);
}
int
kernel_lun_resize(struct lun *lun)
{
struct ctl_lun_req req;
bzero(&req, sizeof(req));
strlcpy(req.backend, lun->l_backend, sizeof(req.backend));
req.reqtype = CTL_LUNREQ_MODIFY;
req.reqdata.modify.lun_id = lun->l_ctl_lun;
req.reqdata.modify.lun_size_bytes = lun->l_size;
if (ioctl(ctl_fd, CTL_LUN_REQ, &req) == -1) {
log_warn("error issuing CTL_LUN_REQ ioctl");
return (1);
}
if (req.status == CTL_LUN_ERROR) {
log_warnx("error returned from LUN modification request: %s",
req.error_str);
return (1);
}
if (req.status != CTL_LUN_OK) {
log_warnx("unknown LUN modification request status %d",
req.status);
return (1);
}
return (0);
}
int
kernel_lun_remove(struct lun *lun)
{
struct ctl_lun_req req;
bzero(&req, sizeof(req));
strlcpy(req.backend, lun->l_backend, sizeof(req.backend));
req.reqtype = CTL_LUNREQ_RM;
req.reqdata.rm.lun_id = lun->l_ctl_lun;
if (ioctl(ctl_fd, CTL_LUN_REQ, &req) == -1) {
log_warn("error issuing CTL_LUN_REQ ioctl");
return (1);
}
if (req.status == CTL_LUN_ERROR) {
log_warnx("error returned from LUN removal request: %s",
req.error_str);
return (1);
}
if (req.status != CTL_LUN_OK) {
log_warnx("unknown LUN removal request status %d", req.status);
return (1);
}
return (0);
}
void
kernel_handoff(struct connection *conn)
{
struct ctl_iscsi req;
bzero(&req, sizeof(req));
req.type = CTL_ISCSI_HANDOFF;
strlcpy(req.data.handoff.initiator_name,
conn->conn_initiator_name, sizeof(req.data.handoff.initiator_name));
strlcpy(req.data.handoff.initiator_addr,
conn->conn_initiator_addr, sizeof(req.data.handoff.initiator_addr));
if (conn->conn_initiator_alias != NULL) {
strlcpy(req.data.handoff.initiator_alias,
conn->conn_initiator_alias, sizeof(req.data.handoff.initiator_alias));
}
strlcpy(req.data.handoff.target_name,
conn->conn_target->t_iqn, sizeof(req.data.handoff.target_name));
req.data.handoff.socket = conn->conn_socket;
req.data.handoff.portal_group_tag =
conn->conn_portal->p_portal_group->pg_tag;
if (conn->conn_header_digest == CONN_DIGEST_CRC32C)
req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C;
if (conn->conn_data_digest == CONN_DIGEST_CRC32C)
req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C;
req.data.handoff.cmdsn = conn->conn_cmdsn;
req.data.handoff.statsn = conn->conn_statsn;
req.data.handoff.max_recv_data_segment_length =
conn->conn_max_data_segment_length;
req.data.handoff.max_burst_length = conn->conn_max_burst_length;
req.data.handoff.immediate_data = conn->conn_immediate_data;
if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1)
log_err(1, "error issuing CTL_ISCSI ioctl; "
"dropping connection");
if (req.status != CTL_ISCSI_OK)
log_errx(1, "error returned from CTL iSCSI handoff request: "
"%s; dropping connection", req.error_str);
}
int
kernel_port_on(void)
{
struct ctl_port_entry entry;
int error;
bzero(&entry, sizeof(&entry));
entry.port_type = CTL_PORT_ISCSI;
entry.targ_port = -1;
error = ioctl(ctl_fd, CTL_ENABLE_PORT, &entry);
if (error != 0) {
log_warn("CTL_ENABLE_PORT ioctl failed");
return (-1);
}
return (0);
}
int
kernel_port_off(void)
{
struct ctl_port_entry entry;
int error;
bzero(&entry, sizeof(&entry));
entry.port_type = CTL_PORT_ISCSI;
entry.targ_port = -1;
error = ioctl(ctl_fd, CTL_DISABLE_PORT, &entry);
if (error != 0) {
log_warn("CTL_DISABLE_PORT ioctl failed");
return (-1);
}
return (0);
}
#ifdef ICL_KERNEL_PROXY
void
kernel_listen(struct addrinfo *ai, bool iser)
{
struct ctl_iscsi req;
bzero(&req, sizeof(req));
req.type = CTL_ISCSI_LISTEN;
req.data.listen.iser = iser;
req.data.listen.domain = ai->ai_family;
req.data.listen.socktype = ai->ai_socktype;
req.data.listen.protocol = ai->ai_protocol;
req.data.listen.addr = ai->ai_addr;
req.data.listen.addrlen = ai->ai_addrlen;
if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1)
log_warn("error issuing CTL_ISCSI_LISTEN ioctl");
}
int
kernel_accept(void)
{
struct ctl_iscsi req;
bzero(&req, sizeof(req));
req.type = CTL_ISCSI_ACCEPT;
if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) {
log_warn("error issuing CTL_ISCSI_LISTEN ioctl");
return (0);
}
return (req.data.accept.connection_id);
}
void
kernel_send(struct pdu *pdu)
{
struct ctl_iscsi req;
bzero(&req, sizeof(req));
req.type = CTL_ISCSI_SEND;
req.data.send.connection_id = pdu->pdu_connection->conn_socket;
req.data.send.bhs = pdu->pdu_bhs;
req.data.send.data_segment_len = pdu->pdu_data_len;
req.data.send.data_segment = pdu->pdu_data;
if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1)
log_err(1, "error issuing CTL_ISCSI ioctl; "
"dropping connection");
if (req.status != CTL_ISCSI_OK)
log_errx(1, "error returned from CTL iSCSI send: "
"%s; dropping connection", req.error_str);
}
void
kernel_receive(struct pdu *pdu)
{
struct ctl_iscsi req;
pdu->pdu_data = malloc(MAX_DATA_SEGMENT_LENGTH);
if (pdu->pdu_data == NULL)
log_err(1, "malloc");
bzero(&req, sizeof(req));
req.type = CTL_ISCSI_RECEIVE;
req.data.receive.connection_id = pdu->pdu_connection->conn_socket;
req.data.receive.bhs = pdu->pdu_bhs;
req.data.receive.data_segment_len = MAX_DATA_SEGMENT_LENGTH;
req.data.receive.data_segment = pdu->pdu_data;
if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1)
log_err(1, "error issuing CTL_ISCSI ioctl; "
"dropping connection");
if (req.status != CTL_ISCSI_OK)
log_errx(1, "error returned from CTL iSCSI receive: "
"%s; dropping connection", req.error_str);
}
#endif /* ICL_KERNEL_PROXY */
/*
* XXX: I CANT INTO LATIN
*/
void
kernel_capsicate(void)
{
int error;
cap_rights_t rights;
const unsigned long cmds[] = { CTL_ISCSI };
cap_rights_init(&rights, CAP_IOCTL);
error = cap_rights_limit(ctl_fd, &rights);
if (error != 0 && errno != ENOSYS)
log_err(1, "cap_rights_limit");
error = cap_ioctls_limit(ctl_fd, cmds,
sizeof(cmds) / sizeof(cmds[0]));
if (error != 0 && errno != ENOSYS)
log_err(1, "cap_ioctls_limit");
error = cap_enter();
if (error != 0 && errno != ENOSYS)
log_err(1, "cap_enter");
if (cap_sandboxed())
log_debugx("Capsicum capability mode enabled");
else
log_warnx("Capsicum capability mode not supported");
}

216
usr.sbin/ctld/keys.c Normal file
View File

@ -0,0 +1,216 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ctld.h"
struct keys *
keys_new(void)
{
struct keys *keys;
keys = calloc(sizeof(*keys), 1);
if (keys == NULL)
log_err(1, "calloc");
return (keys);
}
void
keys_delete(struct keys *keys)
{
free(keys->keys_data);
free(keys);
}
void
keys_load(struct keys *keys, const struct pdu *pdu)
{
int i;
char *pair;
size_t pair_len;
if (pdu->pdu_data_len == 0)
log_errx(1, "protocol error: empty data segment");
if (pdu->pdu_data[pdu->pdu_data_len - 1] != '\0')
log_errx(1, "protocol error: key not NULL-terminated\n");
assert(keys->keys_data == NULL);
keys->keys_data_len = pdu->pdu_data_len;
keys->keys_data = malloc(keys->keys_data_len);
if (keys->keys_data == NULL)
log_err(1, "malloc");
memcpy(keys->keys_data, pdu->pdu_data, keys->keys_data_len);
/*
* XXX: Review this carefully.
*/
pair = keys->keys_data;
for (i = 0;; i++) {
if (i >= KEYS_MAX)
log_errx(1, "too many keys received");
pair_len = strlen(pair);
keys->keys_values[i] = pair;
keys->keys_names[i] = strsep(&keys->keys_values[i], "=");
if (keys->keys_names[i] == NULL || keys->keys_values[i] == NULL)
log_errx(1, "malformed keys");
log_debugx("key received: \"%s=%s\"",
keys->keys_names[i], keys->keys_values[i]);
pair += pair_len + 1; /* +1 to skip the terminating '\0'. */
if (pair == keys->keys_data + keys->keys_data_len)
break;
assert(pair < keys->keys_data + keys->keys_data_len);
}
}
void
keys_save(struct keys *keys, struct pdu *pdu)
{
char *data;
size_t len;
int i;
/*
* XXX: Not particularly efficient.
*/
len = 0;
for (i = 0; i < KEYS_MAX; i++) {
if (keys->keys_names[i] == NULL)
break;
/*
* +1 for '=', +1 for '\0'.
*/
len += strlen(keys->keys_names[i]) +
strlen(keys->keys_values[i]) + 2;
}
if (len == 0)
return;
data = malloc(len);
if (data == NULL)
log_err(1, "malloc");
pdu->pdu_data = data;
pdu->pdu_data_len = len;
for (i = 0; i < KEYS_MAX; i++) {
if (keys->keys_names[i] == NULL)
break;
data += sprintf(data, "%s=%s",
keys->keys_names[i], keys->keys_values[i]);
data += 1; /* for '\0'. */
}
}
const char *
keys_find(struct keys *keys, const char *name)
{
int i;
/*
* Note that we don't handle duplicated key names here,
* as they are not supposed to happen in requests, and if they do,
* it's an initiator error.
*/
for (i = 0; i < KEYS_MAX; i++) {
if (keys->keys_names[i] == NULL)
return (NULL);
if (strcmp(keys->keys_names[i], name) == 0)
return (keys->keys_values[i]);
}
return (NULL);
}
int
keys_find_int(struct keys *keys, const char *name)
{
const char *str;
char *endptr;
int num;
str = keys_find(keys, name);
if (str == NULL)
return (-1);
num = strtoul(str, &endptr, 10);
if (*endptr != '\0') {
log_debugx("invalid numeric value \"%s\"", str);
return (-1);
}
return (num);
}
void
keys_add(struct keys *keys, const char *name, const char *value)
{
int i;
log_debugx("key to send: \"%s=%s\"", name, value);
/*
* Note that we don't check for duplicates here, as they are perfectly
* fine in responses, e.g. the "TargetName" keys in discovery sesion
* response.
*/
for (i = 0; i < KEYS_MAX; i++) {
if (keys->keys_names[i] == NULL) {
keys->keys_names[i] = checked_strdup(name);
keys->keys_values[i] = checked_strdup(value);
return;
}
}
log_errx(1, "too many keys");
}
void
keys_add_int(struct keys *keys, const char *name, int value)
{
char *str;
int ret;
ret = asprintf(&str, "%d", value);
if (ret <= 0)
log_err(1, "asprintf");
keys_add(keys, name, str);
free(str);
}

196
usr.sbin/ctld/log.c Normal file
View File

@ -0,0 +1,196 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <vis.h>
#include "ctld.h"
static int log_level = 0;
static char *peer_name = NULL;
static char *peer_addr = NULL;
#define MSGBUF_LEN 1024
void
log_init(int level)
{
log_level = level;
openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON);
}
void
log_set_peer_name(const char *name)
{
/*
* XXX: Turn it into assertion?
*/
if (peer_name != NULL)
log_errx(1, "%s called twice", __func__);
if (peer_addr == NULL)
log_errx(1, "%s called before log_set_peer_addr", __func__);
peer_name = checked_strdup(name);
}
void
log_set_peer_addr(const char *addr)
{
/*
* XXX: Turn it into assertion?
*/
if (peer_addr != NULL)
log_errx(1, "%s called twice", __func__);
peer_addr = checked_strdup(addr);
}
static void
log_common(int priority, int log_errno, const char *fmt, va_list ap)
{
static char msgbuf[MSGBUF_LEN];
static char msgbuf_strvised[MSGBUF_LEN * 4 + 1];
int ret;
ret = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
if (ret < 0) {
fprintf(stderr, "%s: snprintf failed", getprogname());
syslog(LOG_CRIT, "snprintf failed");
exit(1);
}
ret = strnvis(msgbuf_strvised, sizeof(msgbuf_strvised), msgbuf, VIS_NL);
if (ret < 0) {
fprintf(stderr, "%s: strnvis failed", getprogname());
syslog(LOG_CRIT, "strnvis failed");
exit(1);
}
if (log_errno == -1) {
if (peer_name != NULL) {
fprintf(stderr, "%s: %s (%s): %s\n", getprogname(),
peer_addr, peer_name, msgbuf_strvised);
syslog(priority, "%s (%s): %s",
peer_addr, peer_name, msgbuf_strvised);
} else if (peer_addr != NULL) {
fprintf(stderr, "%s: %s: %s\n", getprogname(),
peer_addr, msgbuf_strvised);
syslog(priority, "%s: %s",
peer_addr, msgbuf_strvised);
} else {
fprintf(stderr, "%s: %s\n", getprogname(), msgbuf_strvised);
syslog(priority, "%s", msgbuf_strvised);
}
} else {
if (peer_name != NULL) {
fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(),
peer_addr, peer_name, msgbuf_strvised, strerror(errno));
syslog(priority, "%s (%s): %s: %s",
peer_addr, peer_name, msgbuf_strvised, strerror(errno));
} else if (peer_addr != NULL) {
fprintf(stderr, "%s: %s: %s: %s\n", getprogname(),
peer_addr, msgbuf_strvised, strerror(errno));
syslog(priority, "%s: %s: %s",
peer_addr, msgbuf_strvised, strerror(errno));
} else {
fprintf(stderr, "%s: %s: %s\n", getprogname(),
msgbuf_strvised, strerror(errno));
syslog(priority, "%s: %s",
msgbuf_strvised, strerror(errno));
}
}
}
void
log_err(int eval, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
log_common(LOG_CRIT, errno, fmt, ap);
va_end(ap);
exit(eval);
}
void
log_errx(int eval, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
log_common(LOG_CRIT, -1, fmt, ap);
va_end(ap);
exit(eval);
}
void
log_warn(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
log_common(LOG_WARNING, errno, fmt, ap);
va_end(ap);
}
void
log_warnx(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
log_common(LOG_WARNING, -1, fmt, ap);
va_end(ap);
}
void
log_debugx(const char *fmt, ...)
{
va_list ap;
if (log_level == 0)
return;
va_start(ap, fmt);
log_common(LOG_DEBUG, -1, fmt, ap);
va_end(ap);
}

1051
usr.sbin/ctld/login.c Normal file

File diff suppressed because it is too large Load Diff

621
usr.sbin/ctld/parse.y Normal file
View File

@ -0,0 +1,621 @@
%{
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "ctld.h"
extern FILE *yyin;
extern char *yytext;
extern int lineno;
static struct conf *conf = NULL;
static struct auth_group *auth_group = NULL;
static struct portal_group *portal_group = NULL;
static struct target *target = NULL;
static struct lun *lun = NULL;
extern void yyerror(const char *);
extern int yylex(void);
extern void yyrestart(FILE *);
%}
%token ALIAS AUTH_GROUP BACKEND BLOCKSIZE CHAP CHAP_MUTUAL CLOSING_BRACKET
%token DEBUG DEVICE_ID DISCOVERY_AUTH_GROUP LISTEN LISTEN_ISER LUN MAXPROC NUM
%token OPENING_BRACKET OPTION PATH PIDFILE PORTAL_GROUP SERIAL SIZE STR TARGET
%token TIMEOUT
%union
{
uint64_t num;
char *str;
}
%token <num> NUM
%token <str> STR
%%
statements:
|
statements statement
;
statement:
debug_statement
|
timeout_statement
|
maxproc_statement
|
pidfile_statement
|
auth_group_definition
|
portal_group_definition
|
target_statement
;
debug_statement: DEBUG NUM
{
conf->conf_debug = $2;
}
;
timeout_statement: TIMEOUT NUM
{
conf->conf_timeout = $2;
}
;
maxproc_statement: MAXPROC NUM
{
conf->conf_maxproc = $2;
}
;
pidfile_statement: PIDFILE STR
{
if (conf->conf_pidfile_path != NULL) {
log_warnx("pidfile specified more than once");
free($2);
return (1);
}
conf->conf_pidfile_path = $2;
}
;
auth_group_definition: AUTH_GROUP auth_group_name
OPENING_BRACKET auth_group_entries CLOSING_BRACKET
{
auth_group = NULL;
}
;
auth_group_name: STR
{
auth_group = auth_group_new(conf, $1);
free($1);
if (auth_group == NULL)
return (1);
}
;
auth_group_entries:
|
auth_group_entries auth_group_entry
;
auth_group_entry:
auth_group_chap
|
auth_group_chap_mutual
;
auth_group_chap: CHAP STR STR
{
const struct auth *ca;
ca = auth_new_chap(auth_group, $2, $3);
free($2);
free($3);
if (ca == NULL)
return (1);
}
;
auth_group_chap_mutual: CHAP_MUTUAL STR STR STR STR
{
const struct auth *ca;
ca = auth_new_chap_mutual(auth_group, $2, $3, $4, $5);
free($2);
free($3);
free($4);
free($5);
if (ca == NULL)
return (1);
}
;
portal_group_definition: PORTAL_GROUP portal_group_name
OPENING_BRACKET portal_group_entries CLOSING_BRACKET
{
portal_group = NULL;
}
;
portal_group_name: STR
{
portal_group = portal_group_new(conf, $1);
free($1);
if (portal_group == NULL)
return (1);
}
;
portal_group_entries:
|
portal_group_entries portal_group_entry
;
portal_group_entry:
portal_group_discovery_auth_group
|
portal_group_listen
|
portal_group_listen_iser
;
portal_group_discovery_auth_group: DISCOVERY_AUTH_GROUP STR
{
if (portal_group->pg_discovery_auth_group != NULL) {
log_warnx("discovery-auth-group for portal-group "
"\"%s\" specified more than once",
portal_group->pg_name);
return (1);
}
portal_group->pg_discovery_auth_group =
auth_group_find(conf, $2);
if (portal_group->pg_discovery_auth_group == NULL) {
log_warnx("unknown discovery-auth-group \"%s\" "
"for portal-group \"%s\"",
$2, portal_group->pg_name);
return (1);
}
free($2);
}
;
portal_group_listen: LISTEN STR
{
int error;
error = portal_group_add_listen(portal_group, $2, false);
free($2);
if (error != 0)
return (1);
}
;
portal_group_listen_iser: LISTEN_ISER STR
{
int error;
error = portal_group_add_listen(portal_group, $2, true);
free($2);
if (error != 0)
return (1);
}
;
target_statement: TARGET target_iqn
OPENING_BRACKET target_entries CLOSING_BRACKET
{
target = NULL;
}
;
target_iqn: STR
{
target = target_new(conf, $1);
free($1);
if (target == NULL)
return (1);
}
;
target_entries:
|
target_entries target_entry
;
target_entry:
alias_statement
|
auth_group_statement
|
chap_statement
|
chap_mutual_statement
|
portal_group_statement
|
lun_statement
;
alias_statement: ALIAS STR
{
if (target->t_alias != NULL) {
log_warnx("alias for target \"%s\" "
"specified more than once", target->t_iqn);
return (1);
}
target->t_alias = $2;
}
;
auth_group_statement: AUTH_GROUP STR
{
if (target->t_auth_group != NULL) {
if (target->t_auth_group->ag_name != NULL)
log_warnx("auth-group for target \"%s\" "
"specified more than once", target->t_iqn);
else
log_warnx("cannot mix auth-grup with explicit "
"authorisations for target \"%s\"",
target->t_iqn);
return (1);
}
target->t_auth_group = auth_group_find(conf, $2);
if (target->t_auth_group == NULL) {
log_warnx("unknown auth-group \"%s\" for target "
"\"%s\"", $2, target->t_iqn);
return (1);
}
free($2);
}
;
chap_statement: CHAP STR STR
{
const struct auth *ca;
if (target->t_auth_group != NULL) {
if (target->t_auth_group->ag_name != NULL) {
log_warnx("cannot mix auth-grup with explicit "
"authorisations for target \"%s\"",
target->t_iqn);
free($2);
free($3);
return (1);
}
} else {
target->t_auth_group = auth_group_new(conf, NULL);
if (target->t_auth_group == NULL) {
free($2);
free($3);
return (1);
}
target->t_auth_group->ag_target = target;
}
ca = auth_new_chap(target->t_auth_group, $2, $3);
free($2);
free($3);
if (ca == NULL)
return (1);
}
;
chap_mutual_statement: CHAP_MUTUAL STR STR STR STR
{
const struct auth *ca;
if (target->t_auth_group != NULL) {
if (target->t_auth_group->ag_name != NULL) {
log_warnx("cannot mix auth-grup with explicit "
"authorisations for target \"%s\"",
target->t_iqn);
free($2);
free($3);
free($4);
free($5);
return (1);
}
} else {
target->t_auth_group = auth_group_new(conf, NULL);
if (target->t_auth_group == NULL) {
free($2);
free($3);
free($4);
free($5);
return (1);
}
target->t_auth_group->ag_target = target;
}
ca = auth_new_chap_mutual(target->t_auth_group,
$2, $3, $4, $5);
free($2);
free($3);
free($4);
free($5);
if (ca == NULL)
return (1);
}
;
portal_group_statement: PORTAL_GROUP STR
{
if (target->t_portal_group != NULL) {
log_warnx("portal-group for target \"%s\" "
"specified more than once", target->t_iqn);
free($2);
return (1);
}
target->t_portal_group = portal_group_find(conf, $2);
if (target->t_portal_group == NULL) {
log_warnx("unknown portal-group \"%s\" for target "
"\"%s\"", $2, target->t_iqn);
free($2);
return (1);
}
free($2);
}
;
lun_statement: LUN lun_number
OPENING_BRACKET lun_statement_entries CLOSING_BRACKET
{
lun = NULL;
}
;
lun_number: NUM
{
lun = lun_new(target, $1);
if (lun == NULL)
return (1);
}
;
lun_statement_entries:
|
lun_statement_entries lun_statement_entry
;
lun_statement_entry:
backend_statement
|
blocksize_statement
|
device_id_statement
|
option_statement
|
path_statement
|
serial_statement
|
size_statement
;
backend_statement: BACKEND STR
{
if (lun->l_backend != NULL) {
log_warnx("backend for lun %d, target \"%s\" "
"specified more than once",
lun->l_lun, target->t_iqn);
free($2);
return (1);
}
lun_set_backend(lun, $2);
free($2);
}
;
blocksize_statement: BLOCKSIZE NUM
{
if (lun->l_blocksize != 0) {
log_warnx("blocksize for lun %d, target \"%s\" "
"specified more than once",
lun->l_lun, target->t_iqn);
return (1);
}
lun_set_blocksize(lun, $2);
}
;
device_id_statement: DEVICE_ID STR
{
if (lun->l_device_id != NULL) {
log_warnx("device_id for lun %d, target \"%s\" "
"specified more than once",
lun->l_lun, target->t_iqn);
free($2);
return (1);
}
lun_set_device_id(lun, $2);
free($2);
}
;
option_statement: OPTION STR STR
{
struct lun_option *clo;
clo = lun_option_new(lun, $2, $3);
free($2);
free($3);
if (clo == NULL)
return (1);
}
;
path_statement: PATH STR
{
if (lun->l_path != NULL) {
log_warnx("path for lun %d, target \"%s\" "
"specified more than once",
lun->l_lun, target->t_iqn);
free($2);
return (1);
}
lun_set_path(lun, $2);
free($2);
}
;
serial_statement: SERIAL STR
{
if (lun->l_serial != NULL) {
log_warnx("serial for lun %d, target \"%s\" "
"specified more than once",
lun->l_lun, target->t_iqn);
free($2);
return (1);
}
lun_set_serial(lun, $2);
free($2);
}
;
size_statement: SIZE NUM
{
if (lun->l_size != 0) {
log_warnx("size for lun %d, target \"%s\" "
"specified more than once",
lun->l_lun, target->t_iqn);
return (1);
}
lun_set_size(lun, $2);
}
;
%%
void
yyerror(const char *str)
{
log_warnx("error in configuration file at line %d near '%s': %s",
lineno, yytext, str);
}
static void
check_perms(const char *path)
{
struct stat sb;
int error;
error = stat(path, &sb);
if (error != 0) {
log_warn("stat");
return;
}
if (sb.st_mode & S_IWOTH) {
log_warnx("%s is world-writable", path);
} else if (sb.st_mode & S_IROTH) {
log_warnx("%s is world-readable", path);
} else if (sb.st_mode & S_IXOTH) {
/*
* Ok, this one doesn't matter, but still do it,
* just for consistency.
*/
log_warnx("%s is world-executable", path);
}
/*
* XXX: Should we also check for owner != 0?
*/
}
struct conf *
conf_new_from_file(const char *path)
{
struct auth_group *ag;
struct portal_group *pg;
int error;
log_debugx("obtaining configuration from %s", path);
conf = conf_new();
ag = auth_group_new(conf, "no-authentication");
ag->ag_type = AG_TYPE_NO_AUTHENTICATION;
/*
* Here, the type doesn't really matter, as the group doesn't contain
* any entries and thus will always deny access.
*/
ag = auth_group_new(conf, "no-access");
ag->ag_type = AG_TYPE_CHAP;
pg = portal_group_new(conf, "default");
portal_group_add_listen(pg, "0.0.0.0:3260", false);
portal_group_add_listen(pg, "[::]:3260", false);
yyin = fopen(path, "r");
if (yyin == NULL) {
log_warn("unable to open configuration file %s", path);
conf_delete(conf);
return (NULL);
}
check_perms(path);
lineno = 0;
yyrestart(yyin);
error = yyparse();
auth_group = NULL;
portal_group = NULL;
target = NULL;
lun = NULL;
fclose(yyin);
if (error != 0) {
conf_delete(conf);
return (NULL);
}
error = conf_verify(conf);
if (error != 0) {
conf_delete(conf);
return (NULL);
}
return (conf);
}

246
usr.sbin/ctld/pdu.c Normal file
View File

@ -0,0 +1,246 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/types.h>
#include <sys/uio.h>
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "ctld.h"
#include "iscsi_proto.h"
#ifdef ICL_KERNEL_PROXY
#include <sys/ioctl.h>
#endif
static int
pdu_ahs_length(const struct pdu *pdu)
{
return (pdu->pdu_bhs->bhs_total_ahs_len * 4);
}
static int
pdu_data_segment_length(const struct pdu *pdu)
{
uint32_t len = 0;
len += pdu->pdu_bhs->bhs_data_segment_len[0];
len <<= 8;
len += pdu->pdu_bhs->bhs_data_segment_len[1];
len <<= 8;
len += pdu->pdu_bhs->bhs_data_segment_len[2];
return (len);
}
static void
pdu_set_data_segment_length(struct pdu *pdu, uint32_t len)
{
pdu->pdu_bhs->bhs_data_segment_len[2] = len;
pdu->pdu_bhs->bhs_data_segment_len[1] = len >> 8;
pdu->pdu_bhs->bhs_data_segment_len[0] = len >> 16;
}
struct pdu *
pdu_new(struct connection *conn)
{
struct pdu *pdu;
pdu = calloc(sizeof(*pdu), 1);
if (pdu == NULL)
log_err(1, "calloc");
pdu->pdu_bhs = calloc(sizeof(*pdu->pdu_bhs), 1);
if (pdu->pdu_bhs == NULL)
log_err(1, "calloc");
pdu->pdu_connection = conn;
return (pdu);
}
struct pdu *
pdu_new_response(struct pdu *request)
{
return (pdu_new(request->pdu_connection));
}
#ifdef ICL_KERNEL_PROXY
void
pdu_receive(struct pdu *pdu)
{
size_t len;
kernel_receive(pdu);
len = pdu_ahs_length(pdu);
if (len > 0)
log_errx(1, "protocol error: non-empty AHS");
len = pdu_data_segment_length(pdu);
assert(len <= MAX_DATA_SEGMENT_LENGTH);
pdu->pdu_data_len = len;
}
void
pdu_send(struct pdu *pdu)
{
pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
kernel_send(pdu);
}
#else /* !ICL_KERNEL_PROXY */
static size_t
pdu_padding(const struct pdu *pdu)
{
if ((pdu->pdu_data_len % 4) != 0)
return (4 - (pdu->pdu_data_len % 4));
return (0);
}
static void
pdu_read(int fd, char *data, size_t len)
{
ssize_t ret;
while (len > 0) {
ret = read(fd, data, len);
if (ret < 0) {
if (timed_out())
log_errx(1, "exiting due to timeout");
log_err(1, "read");
} else if (ret == 0)
log_errx(1, "read: connection lost");
len -= ret;
data += ret;
}
}
void
pdu_receive(struct pdu *pdu)
{
size_t len, padding;
char dummy[4];
pdu_read(pdu->pdu_connection->conn_socket,
(char *)pdu->pdu_bhs, sizeof(*pdu->pdu_bhs));
len = pdu_ahs_length(pdu);
if (len > 0)
log_errx(1, "protocol error: non-empty AHS");
len = pdu_data_segment_length(pdu);
if (len > 0) {
if (len > MAX_DATA_SEGMENT_LENGTH) {
log_errx(1, "protocol error: received PDU "
"with DataSegmentLength exceeding %d",
MAX_DATA_SEGMENT_LENGTH);
}
pdu->pdu_data_len = len;
pdu->pdu_data = malloc(len);
if (pdu->pdu_data == NULL)
log_err(1, "malloc");
pdu_read(pdu->pdu_connection->conn_socket,
(char *)pdu->pdu_data, pdu->pdu_data_len);
padding = pdu_padding(pdu);
if (padding != 0) {
assert(padding < sizeof(dummy));
pdu_read(pdu->pdu_connection->conn_socket,
(char *)dummy, padding);
}
}
}
void
pdu_send(struct pdu *pdu)
{
ssize_t ret, total_len;
size_t padding;
uint32_t zero = 0;
struct iovec iov[3];
int iovcnt;
pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
iov[0].iov_base = pdu->pdu_bhs;
iov[0].iov_len = sizeof(*pdu->pdu_bhs);
total_len = iov[0].iov_len;
iovcnt = 1;
if (pdu->pdu_data_len > 0) {
iov[1].iov_base = pdu->pdu_data;
iov[1].iov_len = pdu->pdu_data_len;
total_len += iov[1].iov_len;
iovcnt = 2;
padding = pdu_padding(pdu);
if (padding > 0) {
assert(padding < sizeof(zero));
iov[2].iov_base = &zero;
iov[2].iov_len = padding;
total_len += iov[2].iov_len;
iovcnt = 3;
}
}
ret = writev(pdu->pdu_connection->conn_socket, iov, iovcnt);
if (ret < 0) {
if (timed_out())
log_errx(1, "exiting due to timeout");
log_err(1, "writev");
}
if (ret != total_len)
log_errx(1, "short write");
}
#endif /* !ICL_KERNEL_PROXY */
void
pdu_delete(struct pdu *pdu)
{
free(pdu->pdu_data);
free(pdu->pdu_bhs);
free(pdu);
}

85
usr.sbin/ctld/token.l Normal file
View File

@ -0,0 +1,85 @@
%{
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "ctld.h"
#include "y.tab.h"
int lineno;
#define YY_DECL int yylex(void)
extern int yylex(void);
%}
%option noinput
%option nounput
%%
alias { return ALIAS; }
auth-group { return AUTH_GROUP; }
backend { return BACKEND; }
blocksize { return BLOCKSIZE; }
chap { return CHAP; }
chap-mutual { return CHAP_MUTUAL; }
debug { return DEBUG; }
device-id { return DEVICE_ID; }
discovery-auth-group { return DISCOVERY_AUTH_GROUP; }
listen { return LISTEN; }
listen-iser { return LISTEN_ISER; }
lun { return LUN; }
maxproc { return MAXPROC; }
option { return OPTION; }
path { return PATH; }
pidfile { return PIDFILE; }
portal-group { return PORTAL_GROUP; }
serial { return SERIAL; }
size { return SIZE; }
target { return TARGET; }
timeout { return TIMEOUT; }
[0-9]+[kKmMgGtTpPeE]? { if (expand_number(yytext, &yylval.num) == 0)
return NUM;
else
return STR;
}
\"[^"]+\" { yylval.str = strndup(yytext + 1,
strlen(yytext) - 2); return STR; }
[a-zA-Z0-9\.\-_/\:\[\]]+ { yylval.str = strdup(yytext); return STR; }
\{ { return OPENING_BRACKET; }
\} { return CLOSING_BRACKET; }
#.*$ /* ignore comments */;
\n { lineno++; }
[ \t]+ /* ignore whitespace */;
%%

16
usr.sbin/iscsid/Makefile Normal file
View File

@ -0,0 +1,16 @@
# $FreeBSD$
PROG= iscsid
SRCS= discovery.c iscsid.c keys.c log.c login.c pdu.c
CFLAGS+= -I${.CURDIR}
CFLAGS+= -I${.CURDIR}/../../sys/cam
CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi
#CFLAGS+= -DICL_KERNEL_PROXY
MAN= iscsid.8
DPADD= ${LIBUTIL}
LDADD= -lcrypto -lssl -lutil
WARNS= 6
.include <bsd.prog.mk>

222
usr.sbin/iscsid/discovery.c Normal file
View File

@ -0,0 +1,222 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include "iscsid.h"
#include "iscsi_proto.h"
static struct pdu *
text_receive(struct connection *conn)
{
struct pdu *response;
struct iscsi_bhs_text_response *bhstr;
response = pdu_new(conn);
pdu_receive(response);
if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_TEXT_RESPONSE)
log_errx(1, "protocol error: received invalid opcode 0x%x",
response->pdu_bhs->bhs_opcode);
bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs;
#if 0
if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) == 0)
log_errx(1, "received Text PDU without the \"F\" flag");
#endif
/*
* XXX: Implement the C flag some day.
*/
if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0)
log_errx(1, "received Text PDU with unsupported \"C\" flag");
if (response->pdu_data_len == 0)
log_errx(1, "received Text PDU with empty data segment");
if (ntohl(bhstr->bhstr_statsn) != conn->conn_statsn + 1) {
log_errx(1, "received Text PDU with wrong StatSN: "
"is %d, should be %d", ntohl(bhstr->bhstr_statsn),
conn->conn_statsn + 1);
}
conn->conn_statsn = ntohl(bhstr->bhstr_statsn);
return (response);
}
static struct pdu *
text_new_request(struct connection *conn)
{
struct pdu *request;
struct iscsi_bhs_text_request *bhstr;
request = pdu_new(conn);
bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
bhstr->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_REQUEST |
ISCSI_BHS_OPCODE_IMMEDIATE;
bhstr->bhstr_flags = BHSTR_FLAGS_FINAL;
bhstr->bhstr_initiator_task_tag = 0;
bhstr->bhstr_target_transfer_tag = 0xffffffff;
bhstr->bhstr_initiator_task_tag = 0; /* XXX */
bhstr->bhstr_cmdsn = 0; /* XXX */
bhstr->bhstr_expstatsn = htonl(conn->conn_statsn + 1);
return (request);
}
static struct pdu *
logout_receive(struct connection *conn)
{
struct pdu *response;
struct iscsi_bhs_logout_response *bhslr;
response = pdu_new(conn);
pdu_receive(response);
if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGOUT_RESPONSE)
log_errx(1, "protocol error: received invalid opcode 0x%x",
response->pdu_bhs->bhs_opcode);
bhslr = (struct iscsi_bhs_logout_response *)response->pdu_bhs;
if (ntohs(bhslr->bhslr_response) != BHSLR_RESPONSE_CLOSED_SUCCESSFULLY)
log_warnx("received Logout Response with reason %d",
ntohs(bhslr->bhslr_response));
if (ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) {
log_errx(1, "received Logout PDU with wrong StatSN: "
"is %d, should be %d", ntohl(bhslr->bhslr_statsn),
conn->conn_statsn + 1);
}
conn->conn_statsn = ntohl(bhslr->bhslr_statsn);
return (response);
}
static struct pdu *
logout_new_request(struct connection *conn)
{
struct pdu *request;
struct iscsi_bhs_logout_request *bhslr;
request = pdu_new(conn);
bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs;
bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_REQUEST |
ISCSI_BHS_OPCODE_IMMEDIATE;
bhslr->bhslr_reason = BHSLR_REASON_CLOSE_SESSION;
bhslr->bhslr_reason |= 0x80;
bhslr->bhslr_initiator_task_tag = 0; /* XXX */
bhslr->bhslr_cmdsn = 0; /* XXX */
bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1);
return (request);
}
static void
kernel_add(const struct connection *conn, const char *target)
{
struct iscsi_session_add isa;
int error;
memset(&isa, 0, sizeof(isa));
memcpy(&isa.isa_conf, &conn->conn_conf, sizeof(isa));
strlcpy(isa.isa_conf.isc_target, target,
sizeof(isa.isa_conf.isc_target));
isa.isa_conf.isc_discovery = 0;
error = ioctl(conn->conn_iscsi_fd, ISCSISADD, &isa);
if (error != 0)
log_warn("failed to add %s: ISCSISADD", target);
}
static void
kernel_remove(const struct connection *conn)
{
struct iscsi_session_remove isr;
int error;
memset(&isr, 0, sizeof(isr));
isr.isr_session_id = conn->conn_session_id;
error = ioctl(conn->conn_iscsi_fd, ISCSISREMOVE, &isr);
if (error != 0)
log_warn("ISCSISREMOVE");
}
void
discovery(struct connection *conn)
{
struct pdu *request, *response;
struct keys *request_keys, *response_keys;
int i;
log_debugx("beginning discovery session");
request = text_new_request(conn);
request_keys = keys_new();
keys_add(request_keys, "SendTargets", "All");
keys_save(request_keys, request);
keys_delete(request_keys);
request_keys = NULL;
pdu_send(request);
pdu_delete(request);
request = NULL;
log_debugx("waiting for Text Response");
response = text_receive(conn);
response_keys = keys_new();
keys_load(response_keys, response);
for (i = 0; i < KEYS_MAX; i++) {
if (response_keys->keys_names[i] == NULL)
break;
if (strcmp(response_keys->keys_names[i], "TargetName") != 0)
continue;
log_debugx("adding target %s", response_keys->keys_values[i]);
/*
* XXX: Validate the target name?
*/
kernel_add(conn, response_keys->keys_values[i]);
}
keys_delete(response_keys);
pdu_delete(response);
log_debugx("removing temporary discovery session");
kernel_remove(conn);
log_debugx("discovery done; logging out");
request = logout_new_request(conn);
pdu_send(request);
request = NULL;
log_debugx("waiting for Logout Response");
response = logout_receive(conn);
pdu_delete(response);
log_debugx("discovery session done");
}

113
usr.sbin/iscsid/iscsid.8 Normal file
View File

@ -0,0 +1,113 @@
.\" Copyright (c) 2012 The FreeBSD Foundation
.\" All rights reserved.
.\"
.\" This software was developed by Edward Tomasz Napierala under sponsorship
.\" from the FreeBSD Foundation.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd September 20, 2012
.Dt ISCSID 8
.Os
.Sh NAME
.Nm iscsid
.Nd iSCSI initiator daemon
.Sh SYNOPSIS
.Nm
.Op Fl P Ar pidfile
.Op Fl d
.Op Fl l Ar loglevel
.Op Fl m Ar maxproc
.Op Fl t Ar seconds
.Sh DESCRIPTION
The
.Nm
daemon is responsible for performing the Login Phase of iSCSI connections,
as well as performing SendTargets discovery.
.Pp
.Pp
Upon startup, the
.Nm
daemon opens the iSCSI initiator device file and waits for kernel requests.
It doesn't use any configuration file; all the information it needs it gets
from the kernel.
.Pp
When the
.Nm
damon is not running, already established iSCSI connections continue
to work.
However, establishing new connections, or recovering existing ones in case
of connection error, is not possible.
.Pp
The following options are available:
.Bl -tag -width ".Fl P Ar pidfile"
.It Fl P Ar pidfile
Specify alternative location of a file where main process PID will be stored.
The default location is /var/run/iscsid.pid.
.It Fl d
Debug mode.
The server sends verbose debug output to standard error, and does not
put itself in the background.
The server will also not fork and will exit after processing one connection.
This option is only intended for debugging the initiator.
.It Fl l Ar loglevel
Specifies debug level.
The default is 0.
.It Fl m Ar maxproc
Specifies limit for concurrently running child processes handling
connections.
The default is 30.
Setting it to 0 disables the limit.
.It Fl t Ar seconds
Specifies timeout for login session, after which the connection
will be forcibly terminated.
The default is 60.
Setting it to 0 disables the timeout.
.El
.Sh FILES
.Bl -tag -width ".Pa /var/run/iscsid.pid" -compact
.It Pa /dev/iscsi
The iSCSI initiator device file.
.It Pa /var/run/iscsid.pid
The default location of the
.Nm
PID file.
.El
.Sh EXIT STATUS
The
.Nm
utility exits 0 on success, and >0 if an error occurs.
.Sh SEE ALSO
.Xr iscsictl 8
.Sh HISTORY
The
.Nm
command appeared in
.Fx 10.0 .
.Sh AUTHORS
The
.Nm
was developed by
.An Edward Tomasz Napierala Aq trasz@FreeBSD.org
under sponsorship from the FreeBSD Foundation.

576
usr.sbin/iscsid/iscsid.c Normal file
View File

@ -0,0 +1,576 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/linker.h>
#include <sys/socket.h>
#include <sys/capability.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libutil.h>
#include "iscsid.h"
static volatile bool sigalrm_received = false;
static int nchildren = 0;
static void
usage(void)
{
fprintf(stderr, "usage: iscsid [-P pidfile][-d][-m maxproc][-t timeout]\n");
exit(1);
}
char *
checked_strdup(const char *s)
{
char *c;
c = strdup(s);
if (c == NULL)
log_err(1, "strdup");
return (c);
}
static int
resolve_addr(const char *address, struct addrinfo **ai)
{
struct addrinfo hints;
char *arg, *addr, *ch;
const char *port;
int error, colons = 0;
arg = checked_strdup(address);
if (arg[0] == '\0') {
log_warnx("empty address");
return (1);
}
if (arg[0] == '[') {
/*
* IPv6 address in square brackets, perhaps with port.
*/
arg++;
addr = strsep(&arg, "]");
if (arg == NULL) {
log_warnx("invalid address %s", address);
return (1);
}
if (arg[0] == '\0') {
port = "3260";
} else if (arg[0] == ':') {
port = arg + 1;
} else {
log_warnx("invalid address %s", address);
return (1);
}
} else {
/*
* Either IPv6 address without brackets - and without
* a port - or IPv4 address. Just count the colons.
*/
for (ch = arg; *ch != '\0'; ch++) {
if (*ch == ':')
colons++;
}
if (colons > 1) {
addr = arg;
port = "3260";
} else {
addr = strsep(&arg, ":");
if (arg == NULL)
port = "3260";
else
port = arg;
}
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
error = getaddrinfo(addr, port, &hints, ai);
if (error != 0) {
log_warnx("getaddrinfo for %s failed: %s",
address, gai_strerror(error));
return (1);
}
return (0);
}
static struct connection *
connection_new(unsigned int session_id, const struct iscsi_session_conf *conf,
int iscsi_fd)
{
struct connection *conn;
struct addrinfo *from_ai, *to_ai;
const char *from_addr, *to_addr;
#ifdef ICL_KERNEL_PROXY
struct iscsi_daemon_connect *idc;
#endif
int error;
conn = calloc(1, sizeof(*conn));
if (conn == NULL)
log_err(1, "calloc");
/*
* Default values, from RFC 3720, section 12.
*/
conn->conn_header_digest = CONN_DIGEST_NONE;
conn->conn_data_digest = CONN_DIGEST_NONE;
conn->conn_initial_r2t = true;
conn->conn_immediate_data = true;
conn->conn_max_data_segment_length = 8192;
conn->conn_max_burst_length = 262144;
conn->conn_first_burst_length = 65536;
conn->conn_session_id = session_id;
/*
* XXX: Should we sanitize this somehow?
*/
memcpy(&conn->conn_conf, conf, sizeof(conn->conn_conf));
from_addr = conn->conn_conf.isc_initiator_addr;
to_addr = conn->conn_conf.isc_target_addr;
if (from_addr[0] != '\0') {
error = resolve_addr(from_addr, &from_ai);
if (error != 0)
log_errx(1, "failed to resolve initiator address %s",
from_addr);
} else {
from_ai = NULL;
}
error = resolve_addr(to_addr, &to_ai);
if (error != 0)
log_errx(1, "failed to resolve target address %s", to_addr);
conn->conn_iscsi_fd = iscsi_fd;
#ifdef ICL_KERNEL_PROXY
idc = calloc(1, sizeof(*idc));
if (idc == NULL)
log_err(1, "calloc");
idc->idc_session_id = conn->conn_session_id;
if (conn->conn_conf.isc_iser)
idc->idc_iser = 1;
idc->idc_domain = to_ai->ai_family;
idc->idc_socktype = to_ai->ai_socktype;
idc->idc_protocol = to_ai->ai_protocol;
if (from_ai != NULL) {
idc->idc_from_addr = from_ai->ai_addr;
idc->idc_from_addrlen = from_ai->ai_addrlen;
}
idc->idc_to_addr = to_ai->ai_addr;
idc->idc_to_addrlen = to_ai->ai_addrlen;
log_debugx("connecting to %s using ICL kernel proxy", to_addr);
error = ioctl(iscsi_fd, ISCSIDCONNECT, idc);
if (error != 0) {
fail(conn, strerror(errno));
log_err(1, "failed to connect to %s using ICL kernel proxy",
to_addr);
}
#else /* !ICL_KERNEL_PROXY */
if (conn->conn_conf.isc_iser)
log_errx(1, "iscsid(8) compiled without ICL_KERNEL_PROXY "
"does not support iSER");
conn->conn_socket = socket(to_ai->ai_family, to_ai->ai_socktype,
to_ai->ai_protocol);
if (conn->conn_socket < 0)
log_err(1, "failed to create socket for %s", from_addr);
if (from_ai != NULL) {
error = bind(conn->conn_socket, from_ai->ai_addr,
from_ai->ai_addrlen);
if (error != 0)
log_err(1, "failed to bind to %s", from_addr);
}
log_debugx("connecting to %s", to_addr);
error = connect(conn->conn_socket, to_ai->ai_addr, to_ai->ai_addrlen);
if (error != 0) {
fail(conn, strerror(errno));
log_err(1, "failed to connect to %s", to_addr);
}
#endif /* !ICL_KERNEL_PROXY */
return (conn);
}
static void
handoff(struct connection *conn)
{
struct iscsi_daemon_handoff *idh;
int error;
log_debugx("handing off connection to the kernel");
idh = calloc(1, sizeof(*idh));
if (idh == NULL)
log_err(1, "calloc");
idh->idh_session_id = conn->conn_session_id;
#ifndef ICL_KERNEL_PROXY
idh->idh_socket = conn->conn_socket;
#endif
strlcpy(idh->idh_target_alias, conn->conn_target_alias,
sizeof(idh->idh_target_alias));
memcpy(idh->idh_isid, conn->conn_isid, sizeof(idh->idh_isid));
idh->idh_statsn = conn->conn_statsn;
idh->idh_header_digest = conn->conn_header_digest;
idh->idh_data_digest = conn->conn_data_digest;
idh->idh_initial_r2t = conn->conn_initial_r2t;
idh->idh_immediate_data = conn->conn_immediate_data;
idh->idh_max_data_segment_length = conn->conn_max_data_segment_length;
idh->idh_max_burst_length = conn->conn_max_burst_length;
idh->idh_first_burst_length = conn->conn_first_burst_length;
error = ioctl(conn->conn_iscsi_fd, ISCSIDHANDOFF, idh);
if (error != 0)
log_err(1, "ISCSIDHANDOFF");
}
void
fail(const struct connection *conn, const char *reason)
{
struct iscsi_daemon_fail *idf;
int error;
idf = calloc(1, sizeof(*idf));
if (idf == NULL)
log_err(1, "calloc");
idf->idf_session_id = conn->conn_session_id;
strlcpy(idf->idf_reason, reason, sizeof(idf->idf_reason));
error = ioctl(conn->conn_iscsi_fd, ISCSIDFAIL, idf);
if (error != 0)
log_err(1, "ISCSIDFAIL");
}
/*
* XXX: I CANT INTO LATIN
*/
static void
capsicate(struct connection *conn)
{
int error;
cap_rights_t rights;
#ifdef ICL_KERNEL_PROXY
const unsigned long cmds[] = { ISCSIDCONNECT, ISCSIDSEND, ISCSIDRECEIVE,
ISCSIDHANDOFF, ISCSIDFAIL, ISCSISADD, ISCSISREMOVE };
#else
const unsigned long cmds[] = { ISCSIDHANDOFF, ISCSIDFAIL, ISCSISADD,
ISCSISREMOVE };
#endif
cap_rights_init(&rights, CAP_IOCTL);
error = cap_rights_limit(conn->conn_iscsi_fd, &rights);
if (error != 0 && errno != ENOSYS)
log_err(1, "cap_rights_limit");
error = cap_ioctls_limit(conn->conn_iscsi_fd, cmds,
sizeof(cmds) / sizeof(cmds[0]));
if (error != 0 && errno != ENOSYS)
log_err(1, "cap_ioctls_limit");
error = cap_enter();
if (error != 0 && errno != ENOSYS)
log_err(1, "cap_enter");
if (cap_sandboxed())
log_debugx("Capsicum capability mode enabled");
else
log_warnx("Capsicum capability mode not supported");
}
bool
timed_out(void)
{
return (sigalrm_received);
}
static void
sigalrm_handler(int dummy __unused)
{
/*
* It would be easiest to just log an error and exit. We can't
* do this, though, because log_errx() is not signal safe, since
* it calls syslog(3). Instead, set a flag checked by pdu_send()
* and pdu_receive(), to call log_errx() there. Should they fail
* to notice, we'll exit here one second later.
*/
if (sigalrm_received) {
/*
* Oh well. Just give up and quit.
*/
_exit(2);
}
sigalrm_received = true;
}
static void
set_timeout(int timeout)
{
struct sigaction sa;
struct itimerval itv;
int error;
if (timeout <= 0) {
log_debugx("session timeout disabled");
return;
}
bzero(&sa, sizeof(sa));
sa.sa_handler = sigalrm_handler;
sigfillset(&sa.sa_mask);
error = sigaction(SIGALRM, &sa, NULL);
if (error != 0)
log_err(1, "sigaction");
/*
* First SIGALRM will arive after conf_timeout seconds.
* If we do nothing, another one will arrive a second later.
*/
bzero(&itv, sizeof(itv));
itv.it_interval.tv_sec = 1;
itv.it_value.tv_sec = timeout;
log_debugx("setting session timeout to %d seconds",
timeout);
error = setitimer(ITIMER_REAL, &itv, NULL);
if (error != 0)
log_err(1, "setitimer");
}
static void
handle_request(int iscsi_fd, struct iscsi_daemon_request *request, int timeout)
{
struct connection *conn;
log_set_peer_addr(request->idr_conf.isc_target_addr);
if (request->idr_conf.isc_target[0] != '\0') {
log_set_peer_name(request->idr_conf.isc_target);
setproctitle("%s (%s)", request->idr_conf.isc_target_addr, request->idr_conf.isc_target);
} else {
setproctitle("%s", request->idr_conf.isc_target_addr);
}
conn = connection_new(request->idr_session_id, &request->idr_conf, iscsi_fd);
set_timeout(timeout);
capsicate(conn);
login(conn);
if (conn->conn_conf.isc_discovery != 0)
discovery(conn);
else
handoff(conn);
log_debugx("nothing more to do; exiting");
exit (0);
}
static int
wait_for_children(bool block)
{
pid_t pid;
int status;
int num = 0;
for (;;) {
/*
* If "block" is true, wait for at least one process.
*/
if (block && num == 0)
pid = wait4(-1, &status, 0, NULL);
else
pid = wait4(-1, &status, WNOHANG, NULL);
if (pid <= 0)
break;
if (WIFSIGNALED(status)) {
log_warnx("child process %d terminated with signal %d",
pid, WTERMSIG(status));
} else if (WEXITSTATUS(status) != 0) {
log_warnx("child process %d terminated with exit status %d",
pid, WEXITSTATUS(status));
} else {
log_debugx("child process %d terminated gracefully", pid);
}
num++;
}
return (num);
}
int
main(int argc, char **argv)
{
int ch, debug = 0, error, iscsi_fd, maxproc = 30, retval, saved_errno,
timeout = 60;
bool dont_daemonize = false;
struct pidfh *pidfh;
pid_t pid, otherpid;
const char *pidfile_path = DEFAULT_PIDFILE;
struct iscsi_daemon_request *request;
while ((ch = getopt(argc, argv, "P:dl:m:t:")) != -1) {
switch (ch) {
case 'P':
pidfile_path = optarg;
break;
case 'd':
dont_daemonize = true;
debug++;
break;
case 'l':
debug = atoi(optarg);
break;
case 'm':
maxproc = atoi(optarg);
break;
case 't':
timeout = atoi(optarg);
break;
case '?':
default:
usage();
}
}
argc -= optind;
if (argc != 0)
usage();
log_init(debug);
pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
if (pidfh == NULL) {
if (errno == EEXIST)
log_errx(1, "daemon already running, pid: %jd.",
(intmax_t)otherpid);
log_err(1, "cannot open or create pidfile \"%s\"",
pidfile_path);
}
iscsi_fd = open(ISCSI_PATH, O_RDWR);
if (iscsi_fd < 0) {
saved_errno = errno;
retval = kldload("iscsi");
if (retval != -1)
iscsi_fd = open(ISCSI_PATH, O_RDWR);
else
errno = saved_errno;
}
if (iscsi_fd < 0)
log_err(1, "failed to open %s", ISCSI_PATH);
if (dont_daemonize == false) {
if (daemon(0, 0) == -1) {
log_warn("cannot daemonize");
pidfile_remove(pidfh);
exit(1);
}
}
pidfile_write(pidfh);
for (;;) {
log_debugx("waiting for request from the kernel");
request = calloc(1, sizeof(*request));
if (request == NULL)
log_err(1, "calloc");
error = ioctl(iscsi_fd, ISCSIDWAIT, request);
if (error != 0) {
if (errno == EINTR) {
nchildren -= wait_for_children(false);
assert(nchildren >= 0);
continue;
}
log_err(1, "ISCSIDWAIT");
}
if (dont_daemonize) {
log_debugx("not forking due to -d flag; "
"will exit after servicing a single request");
} else {
nchildren -= wait_for_children(false);
assert(nchildren >= 0);
while (maxproc > 0 && nchildren >= maxproc) {
log_debugx("maxproc limit of %d child processes hit; "
"waiting for child process to exit", maxproc);
nchildren -= wait_for_children(true);
assert(nchildren >= 0);
}
log_debugx("incoming connection; forking child process #%d",
nchildren);
nchildren++;
pid = fork();
if (pid < 0)
log_err(1, "fork");
if (pid > 0)
continue;
}
pidfile_close(pidfh);
handle_request(iscsi_fd, request, timeout);
}
return (0);
}

120
usr.sbin/iscsid/iscsid.h Normal file
View File

@ -0,0 +1,120 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef ISCSID_H
#define ISCSID_H
#include <stdbool.h>
#include <stdint.h>
#include <iscsi_ioctl.h>
#define DEFAULT_PIDFILE "/var/run/iscsid.pid"
#define CONN_DIGEST_NONE 0
#define CONN_DIGEST_CRC32C 1
#define CONN_MUTUAL_CHALLENGE_LEN 1024
struct connection {
int conn_iscsi_fd;
#ifndef ICL_KERNEL_PROXY
int conn_socket;
#endif
unsigned int conn_session_id;
struct iscsi_session_conf conn_conf;
char conn_target_alias[ISCSI_ADDR_LEN];
uint8_t conn_isid[6];
uint32_t conn_statsn;
int conn_header_digest;
int conn_data_digest;
bool conn_initial_r2t;
bool conn_immediate_data;
size_t conn_max_data_segment_length;
size_t conn_max_burst_length;
size_t conn_first_burst_length;
char conn_mutual_challenge[CONN_MUTUAL_CHALLENGE_LEN];
unsigned char conn_mutual_id;
};
struct pdu {
struct connection *pdu_connection;
struct iscsi_bhs *pdu_bhs;
char *pdu_data;
size_t pdu_data_len;
};
#define KEYS_MAX 1024
struct keys {
char *keys_names[KEYS_MAX];
char *keys_values[KEYS_MAX];
char *keys_data;
size_t keys_data_len;
};
struct keys *keys_new(void);
void keys_delete(struct keys *key);
void keys_load(struct keys *keys, const struct pdu *pdu);
void keys_save(struct keys *keys, struct pdu *pdu);
const char *keys_find(struct keys *keys, const char *name);
int keys_find_int(struct keys *keys, const char *name);
void keys_add(struct keys *keys,
const char *name, const char *value);
void keys_add_int(struct keys *keys,
const char *name, int value);
struct pdu *pdu_new(struct connection *ic);
struct pdu *pdu_new_response(struct pdu *request);
void pdu_receive(struct pdu *request);
void pdu_send(struct pdu *response);
void pdu_delete(struct pdu *ip);
void login(struct connection *ic);
void discovery(struct connection *ic);
void log_init(int level);
void log_set_peer_name(const char *name);
void log_set_peer_addr(const char *addr);
void log_err(int, const char *, ...)
__dead2 __printf0like(2, 3);
void log_errx(int, const char *, ...)
__dead2 __printf0like(2, 3);
void log_warn(const char *, ...) __printf0like(1, 2);
void log_warnx(const char *, ...) __printflike(1, 2);
void log_debugx(const char *, ...) __printf0like(1, 2);
char *checked_strdup(const char *);
bool timed_out(void);
void fail(const struct connection *, const char *);
#endif /* !ISCSID_H */

217
usr.sbin/iscsid/keys.c Normal file
View File

@ -0,0 +1,217 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "iscsid.h"
struct keys *
keys_new(void)
{
struct keys *keys;
keys = calloc(sizeof(*keys), 1);
if (keys == NULL)
log_err(1, "calloc");
return (keys);
}
void
keys_delete(struct keys *keys)
{
free(keys->keys_data);
free(keys);
}
void
keys_load(struct keys *keys, const struct pdu *pdu)
{
int i;
char *pair;
size_t pair_len;
if (pdu->pdu_data_len == 0)
log_errx(1, "protocol error: empty data segment");
if (pdu->pdu_data[pdu->pdu_data_len - 1] != '\0')
log_errx(1, "protocol error: key not NULL-terminated\n");
assert(keys->keys_data == NULL);
keys->keys_data_len = pdu->pdu_data_len;
keys->keys_data = malloc(keys->keys_data_len);
if (keys->keys_data == NULL)
log_err(1, "malloc");
memcpy(keys->keys_data, pdu->pdu_data, keys->keys_data_len);
/*
* XXX: Review this carefully.
*/
pair = keys->keys_data;
for (i = 0;; i++) {
if (i >= KEYS_MAX)
log_errx(1, "too many keys received");
pair_len = strlen(pair);
keys->keys_values[i] = pair;
keys->keys_names[i] = strsep(&keys->keys_values[i], "=");
if (keys->keys_names[i] == NULL || keys->keys_values[i] == NULL)
log_errx(1, "malformed keys");
log_debugx("key received: \"%s=%s\"",
keys->keys_names[i], keys->keys_values[i]);
pair += pair_len + 1; /* +1 to skip the terminating '\0'. */
if (pair == keys->keys_data + keys->keys_data_len)
break;
assert(pair < keys->keys_data + keys->keys_data_len);
}
}
void
keys_save(struct keys *keys, struct pdu *pdu)
{
char *data;
size_t len;
int i;
/*
* XXX: Not particularly efficient.
*/
len = 0;
for (i = 0; i < KEYS_MAX; i++) {
if (keys->keys_names[i] == NULL)
break;
/*
* +1 for '=', +1 for '\0'.
*/
len += strlen(keys->keys_names[i]) +
strlen(keys->keys_values[i]) + 2;
}
if (len == 0)
return;
data = malloc(len);
if (data == NULL)
log_err(1, "malloc");
pdu->pdu_data = data;
pdu->pdu_data_len = len;
for (i = 0; i < KEYS_MAX; i++) {
if (keys->keys_names[i] == NULL)
break;
data += sprintf(data, "%s=%s",
keys->keys_names[i], keys->keys_values[i]);
data += 1; /* for '\0'. */
}
}
const char *
keys_find(struct keys *keys, const char *name)
{
int i;
/*
* Note that we don't handle duplicated key names here,
* as they are not supposed to happen in requests, and if they do,
* it's an initiator error.
*/
for (i = 0; i < KEYS_MAX; i++) {
if (keys->keys_names[i] == NULL)
return (NULL);
if (strcmp(keys->keys_names[i], name) == 0)
return (keys->keys_values[i]);
}
return (NULL);
}
int
keys_find_int(struct keys *keys, const char *name)
{
const char *str;
char *endptr;
int num;
str = keys_find(keys, name);
if (str == NULL)
return (-1);
num = strtoul(str, &endptr, 10);
if (*endptr != '\0') {
log_debugx("invalid numeric value \"%s\"", str);
return (-1);
}
return (num);
}
void
keys_add(struct keys *keys, const char *name, const char *value)
{
int i;
log_debugx("key to send: \"%s=%s\"", name, value);
/*
* Note that we don't check for duplicates here, as they are perfectly
* fine in responses, e.g. the "TargetName" keys in discovery sesion
* response.
*/
for (i = 0; i < KEYS_MAX; i++) {
if (keys->keys_names[i] == NULL) {
keys->keys_names[i] = checked_strdup(name);
keys->keys_values[i] = checked_strdup(value);
return;
}
}
log_errx(1, "too many keys");
}
void
keys_add_int(struct keys *keys, const char *name, int value)
{
char *str;
int ret;
ret = asprintf(&str, "%d", value);
if (ret <= 0)
log_err(1, "asprintf");
keys_add(keys, name, str);
free(str);
}

196
usr.sbin/iscsid/log.c Normal file
View File

@ -0,0 +1,196 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <vis.h>
#include "iscsid.h"
static int log_level = 0;
static char *peer_name = NULL;
static char *peer_addr = NULL;
#define MSGBUF_LEN 1024
void
log_init(int level)
{
log_level = level;
openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON);
}
void
log_set_peer_name(const char *name)
{
/*
* XXX: Turn it into assertion?
*/
if (peer_name != NULL)
log_errx(1, "%s called twice", __func__);
if (peer_addr == NULL)
log_errx(1, "%s called before log_set_peer_addr", __func__);
peer_name = checked_strdup(name);
}
void
log_set_peer_addr(const char *addr)
{
/*
* XXX: Turn it into assertion?
*/
if (peer_addr != NULL)
log_errx(1, "%s called twice", __func__);
peer_addr = checked_strdup(addr);
}
static void
log_common(int priority, int log_errno, const char *fmt, va_list ap)
{
static char msgbuf[MSGBUF_LEN];
static char msgbuf_strvised[MSGBUF_LEN * 4 + 1];
int ret;
ret = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
if (ret < 0) {
fprintf(stderr, "%s: snprintf failed", getprogname());
syslog(LOG_CRIT, "snprintf failed");
exit(1);
}
ret = strnvis(msgbuf_strvised, sizeof(msgbuf_strvised), msgbuf, VIS_NL);
if (ret < 0) {
fprintf(stderr, "%s: strnvis failed", getprogname());
syslog(LOG_CRIT, "strnvis failed");
exit(1);
}
if (log_errno == -1) {
if (peer_name != NULL) {
fprintf(stderr, "%s: %s (%s): %s\n", getprogname(),
peer_addr, peer_name, msgbuf_strvised);
syslog(priority, "%s (%s): %s",
peer_addr, peer_name, msgbuf_strvised);
} else if (peer_addr != NULL) {
fprintf(stderr, "%s: %s: %s\n", getprogname(),
peer_addr, msgbuf_strvised);
syslog(priority, "%s: %s",
peer_addr, msgbuf_strvised);
} else {
fprintf(stderr, "%s: %s\n", getprogname(), msgbuf_strvised);
syslog(priority, "%s", msgbuf_strvised);
}
} else {
if (peer_name != NULL) {
fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(),
peer_addr, peer_name, msgbuf_strvised, strerror(errno));
syslog(priority, "%s (%s): %s: %s",
peer_addr, peer_name, msgbuf_strvised, strerror(errno));
} else if (peer_addr != NULL) {
fprintf(stderr, "%s: %s: %s: %s\n", getprogname(),
peer_addr, msgbuf_strvised, strerror(errno));
syslog(priority, "%s: %s: %s",
peer_addr, msgbuf_strvised, strerror(errno));
} else {
fprintf(stderr, "%s: %s: %s\n", getprogname(),
msgbuf_strvised, strerror(errno));
syslog(priority, "%s: %s",
msgbuf_strvised, strerror(errno));
}
}
}
void
log_err(int eval, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
log_common(LOG_CRIT, errno, fmt, ap);
va_end(ap);
exit(eval);
}
void
log_errx(int eval, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
log_common(LOG_CRIT, -1, fmt, ap);
va_end(ap);
exit(eval);
}
void
log_warn(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
log_common(LOG_WARNING, errno, fmt, ap);
va_end(ap);
}
void
log_warnx(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
log_common(LOG_WARNING, -1, fmt, ap);
va_end(ap);
}
void
log_debugx(const char *fmt, ...)
{
va_list ap;
if (log_level == 0)
return;
va_start(ap, fmt);
log_common(LOG_DEBUG, -1, fmt, ap);
va_end(ap);
}

868
usr.sbin/iscsid/login.c Normal file
View File

@ -0,0 +1,868 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/types.h>
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <openssl/err.h>
#include <openssl/md5.h>
#include <openssl/rand.h>
#include "iscsid.h"
#include "iscsi_proto.h"
static int
login_nsg(const struct pdu *response)
{
struct iscsi_bhs_login_response *bhslr;
bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
return (bhslr->bhslr_flags & 0x03);
}
static void
login_set_nsg(struct pdu *request, int nsg)
{
struct iscsi_bhs_login_request *bhslr;
assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
nsg == BHSLR_STAGE_FULL_FEATURE_PHASE);
bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
bhslr->bhslr_flags &= 0xFC;
bhslr->bhslr_flags |= nsg;
}
static void
login_set_csg(struct pdu *request, int csg)
{
struct iscsi_bhs_login_request *bhslr;
assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
csg == BHSLR_STAGE_FULL_FEATURE_PHASE);
bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
bhslr->bhslr_flags &= 0xF3;
bhslr->bhslr_flags |= csg << 2;
}
static const char *
login_target_error_str(int class, int detail)
{
static char msg[128];
/*
* RFC 3270, 10.13.5. Status-Class and Status-Detail
*/
switch (class) {
case 0x01:
switch (detail) {
case 0x01:
return ("Target moved temporarily");
case 0x02:
return ("Target moved permanently");
default:
snprintf(msg, sizeof(msg), "unknown redirection; "
"Status-Class 0x%x, Status-Detail 0x%x",
class, detail);
return (msg);
}
case 0x02:
switch (detail) {
case 0x00:
return ("Initiator error");
case 0x01:
return ("Authentication failure");
case 0x02:
return ("Authorization failure");
case 0x03:
return ("Not found");
case 0x04:
return ("Target removed");
case 0x05:
return ("Unsupported version");
case 0x06:
return ("Too many connections");
case 0x07:
return ("Missing parameter");
case 0x08:
return ("Can't include in session");
case 0x09:
return ("Session type not supported");
case 0x0a:
return ("Session does not exist");
case 0x0b:
return ("Invalid during login");
default:
snprintf(msg, sizeof(msg), "unknown initiator error; "
"Status-Class 0x%x, Status-Detail 0x%x",
class, detail);
return (msg);
}
case 0x03:
switch (detail) {
case 0x00:
return ("Target error");
case 0x01:
return ("Service unavailable");
case 0x02:
return ("Out of resources");
default:
snprintf(msg, sizeof(msg), "unknown target error; "
"Status-Class 0x%x, Status-Detail 0x%x",
class, detail);
return (msg);
}
default:
snprintf(msg, sizeof(msg), "unknown error; "
"Status-Class 0x%x, Status-Detail 0x%x",
class, detail);
return (msg);
}
}
static struct pdu *
login_receive(struct connection *conn, bool initial)
{
struct pdu *response;
struct iscsi_bhs_login_response *bhslr;
const char *errorstr;
response = pdu_new(conn);
pdu_receive(response);
if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGIN_RESPONSE) {
log_errx(1, "protocol error: received invalid opcode 0x%x",
response->pdu_bhs->bhs_opcode);
}
bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
/*
* XXX: Implement the C flag some day.
*/
if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0)
log_errx(1, "received Login PDU with unsupported \"C\" flag");
if (bhslr->bhslr_version_max != 0x00)
log_errx(1, "received Login PDU with unsupported "
"Version-max 0x%x", bhslr->bhslr_version_max);
if (bhslr->bhslr_version_active != 0x00)
log_errx(1, "received Login PDU with unsupported "
"Version-active 0x%x", bhslr->bhslr_version_active);
if (bhslr->bhslr_status_class != 0) {
errorstr = login_target_error_str(bhslr->bhslr_status_class,
bhslr->bhslr_status_detail);
fail(conn, errorstr);
log_errx(1, "target returned error: %s", errorstr);
}
#if 0
if (response->pdu_data_len == 0)
log_errx(1, "received Login PDU with empty data segment");
#endif
if (initial == false &&
ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) {
/*
* It's a warning, not an error, to work around what seems
* to be bug in NetBSD iSCSI target.
*/
log_warnx("received Login PDU with wrong StatSN: "
"is %d, should be %d", ntohl(bhslr->bhslr_statsn),
conn->conn_statsn + 1);
}
conn->conn_statsn = ntohl(bhslr->bhslr_statsn);
return (response);
}
static struct pdu *
login_new_request(struct connection *conn)
{
struct pdu *request;
struct iscsi_bhs_login_request *bhslr;
request = pdu_new(conn);
bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_REQUEST |
ISCSI_BHS_OPCODE_IMMEDIATE;
bhslr->bhslr_flags = BHSLR_FLAGS_TRANSIT;
login_set_csg(request, BHSLR_STAGE_SECURITY_NEGOTIATION);
login_set_nsg(request, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
memcpy(bhslr->bhslr_isid, &conn->conn_isid, sizeof(bhslr->bhslr_isid));
bhslr->bhslr_initiator_task_tag = 0;
bhslr->bhslr_cmdsn = 0;
bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1);
return (request);
}
static int
login_list_prefers(const char *list,
const char *choice1, const char *choice2)
{
char *tofree, *str, *token;
tofree = str = checked_strdup(list);
while ((token = strsep(&str, ",")) != NULL) {
if (strcmp(token, choice1) == 0) {
free(tofree);
return (1);
}
if (strcmp(token, choice2) == 0) {
free(tofree);
return (2);
}
}
free(tofree);
return (-1);
}
static int
login_hex2int(const char hex)
{
switch (hex) {
case '0':
return (0x00);
case '1':
return (0x01);
case '2':
return (0x02);
case '3':
return (0x03);
case '4':
return (0x04);
case '5':
return (0x05);
case '6':
return (0x06);
case '7':
return (0x07);
case '8':
return (0x08);
case '9':
return (0x09);
case 'a':
case 'A':
return (0x0a);
case 'b':
case 'B':
return (0x0b);
case 'c':
case 'C':
return (0x0c);
case 'd':
case 'D':
return (0x0d);
case 'e':
case 'E':
return (0x0e);
case 'f':
case 'F':
return (0x0f);
default:
return (-1);
}
}
/*
* XXX: Review this _carefully_.
*/
static int
login_hex2bin(const char *hex, char **binp, size_t *bin_lenp)
{
int i, hex_len, nibble;
bool lo = true; /* As opposed to 'hi'. */
char *bin;
size_t bin_off, bin_len;
if (strncasecmp(hex, "0x", strlen("0x")) != 0) {
log_warnx("malformed variable, should start with \"0x\"");
return (-1);
}
hex += strlen("0x");
hex_len = strlen(hex);
if (hex_len < 1) {
log_warnx("malformed variable; doesn't contain anything "
"but \"0x\"");
return (-1);
}
bin_len = hex_len / 2 + hex_len % 2;
bin = calloc(bin_len, 1);
if (bin == NULL)
log_err(1, "calloc");
bin_off = bin_len - 1;
for (i = hex_len - 1; i >= 0; i--) {
nibble = login_hex2int(hex[i]);
if (nibble < 0) {
log_warnx("malformed variable, invalid char \"%c\"",
hex[i]);
return (-1);
}
assert(bin_off < bin_len);
if (lo) {
bin[bin_off] = nibble;
lo = false;
} else {
bin[bin_off] |= nibble << 4;
bin_off--;
lo = true;
}
}
*binp = bin;
*bin_lenp = bin_len;
return (0);
}
static char *
login_bin2hex(const char *bin, size_t bin_len)
{
unsigned char *hex, *tmp, ch;
size_t hex_len;
size_t i;
hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */
hex = malloc(hex_len);
if (hex == NULL)
log_err(1, "malloc");
tmp = hex;
tmp += sprintf(tmp, "0x");
for (i = 0; i < bin_len; i++) {
ch = bin[i];
tmp += sprintf(tmp, "%02x", ch);
}
return (hex);
}
static void
login_compute_md5(const char id, const char *secret,
const void *challenge, size_t challenge_len, void *response,
size_t response_len)
{
MD5_CTX ctx;
int rv;
assert(response_len == MD5_DIGEST_LENGTH);
MD5_Init(&ctx);
MD5_Update(&ctx, &id, sizeof(id));
MD5_Update(&ctx, secret, strlen(secret));
MD5_Update(&ctx, challenge, challenge_len);
rv = MD5_Final(response, &ctx);
if (rv != 1)
log_errx(1, "MD5_Final");
}
static void
login_negotiate_key(struct connection *conn, const char *name,
const char *value)
{
int which, tmp;
if (strcmp(name, "TargetAlias") == 0) {
strlcpy(conn->conn_target_alias, value,
sizeof(conn->conn_target_alias));
} else if (strcmp(value, "Irrelevant") == 0) {
/* Ignore. */
} else if (strcmp(name, "HeaderDigest") == 0) {
which = login_list_prefers(value, "CRC32C", "None");
switch (which) {
case 1:
log_debugx("target prefers CRC32C "
"for header digest; we'll use it");
conn->conn_header_digest = CONN_DIGEST_CRC32C;
break;
case 2:
log_debugx("target prefers not to do "
"header digest; we'll comply");
break;
default:
log_warnx("target sent unrecognized "
"HeaderDigest value \"%s\"; will use None", value);
break;
}
} else if (strcmp(name, "DataDigest") == 0) {
which = login_list_prefers(value, "CRC32C", "None");
switch (which) {
case 1:
log_debugx("target prefers CRC32C "
"for data digest; we'll use it");
conn->conn_data_digest = CONN_DIGEST_CRC32C;
break;
case 2:
log_debugx("target prefers not to do "
"data digest; we'll comply");
break;
default:
log_warnx("target sent unrecognized "
"DataDigest value \"%s\"; will use None", value);
break;
}
} else if (strcmp(name, "MaxConnections") == 0) {
/* Ignore. */
} else if (strcmp(name, "InitialR2T") == 0) {
if (strcmp(value, "Yes") == 0)
conn->conn_initial_r2t = true;
else
conn->conn_initial_r2t = false;
} else if (strcmp(name, "ImmediateData") == 0) {
if (strcmp(value, "Yes") == 0)
conn->conn_immediate_data = true;
else
conn->conn_immediate_data = false;
} else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) {
tmp = strtoul(value, NULL, 10);
if (tmp <= 0)
log_errx(1, "received invalid "
"MaxRecvDataSegmentLength");
conn->conn_max_data_segment_length = tmp;
} else if (strcmp(name, "MaxBurstLength") == 0) {
if (conn->conn_immediate_data) {
tmp = strtoul(value, NULL, 10);
if (tmp <= 0)
log_errx(1, "received invalid MaxBurstLength");
conn->conn_max_burst_length = tmp;
}
} else if (strcmp(name, "FirstBurstLength") == 0) {
tmp = strtoul(value, NULL, 10);
if (tmp <= 0)
log_errx(1, "received invalid FirstBurstLength");
conn->conn_first_burst_length = tmp;
} else if (strcmp(name, "DefaultTime2Wait") == 0) {
/* Ignore */
} else if (strcmp(name, "DefaultTime2Retain") == 0) {
/* Ignore */
} else if (strcmp(name, "MaxOutstandingR2T") == 0) {
/* Ignore */
} else if (strcmp(name, "DataPDUInOrder") == 0) {
/* Ignore */
} else if (strcmp(name, "DataSequenceInOrder") == 0) {
/* Ignore */
} else if (strcmp(name, "ErrorRecoveryLevel") == 0) {
/* Ignore */
} else if (strcmp(name, "OFMarker") == 0) {
/* Ignore */
} else if (strcmp(name, "IFMarker") == 0) {
/* Ignore */
} else if (strcmp(name, "TargetPortalGroupTag") == 0) {
/* Ignore */
} else {
log_debugx("unknown key \"%s\"; ignoring", name);
}
}
static void
login_negotiate(struct connection *conn)
{
struct pdu *request, *response;
struct keys *request_keys, *response_keys;
struct iscsi_bhs_login_response *bhslr;
int i;
log_debugx("beginning parameter negotiation");
request = login_new_request(conn);
login_set_csg(request, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
login_set_nsg(request, BHSLR_STAGE_FULL_FEATURE_PHASE);
request_keys = keys_new();
if (conn->conn_conf.isc_discovery == 0) {
if (conn->conn_conf.isc_header_digest != 0)
keys_add(request_keys, "HeaderDigest", "CRC32C");
if (conn->conn_conf.isc_data_digest != 0)
keys_add(request_keys, "DataDigest", "CRC32C");
keys_add(request_keys, "ImmediateData", "Yes");
keys_add_int(request_keys, "MaxBurstLength",
ISCSI_MAX_DATA_SEGMENT_LENGTH);
keys_add_int(request_keys, "FirstBurstLength",
ISCSI_MAX_DATA_SEGMENT_LENGTH);
}
keys_add(request_keys, "InitialR2T", "Yes");
keys_add_int(request_keys, "MaxRecvDataSegmentLength",
ISCSI_MAX_DATA_SEGMENT_LENGTH);
keys_add(request_keys, "DefaultTime2Wait", "0");
keys_add(request_keys, "DefaultTime2Retain", "0");
keys_save(request_keys, request);
keys_delete(request_keys);
request_keys = NULL;
pdu_send(request);
pdu_delete(request);
request = NULL;
response = login_receive(conn, false);
response_keys = keys_new();
keys_load(response_keys, response);
for (i = 0; i < KEYS_MAX; i++) {
if (response_keys->keys_names[i] == NULL)
break;
login_negotiate_key(conn,
response_keys->keys_names[i], response_keys->keys_values[i]);
}
bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0)
log_warnx("received final login response "
"without the \"T\" flag");
else if (login_nsg(response) != BHSLR_STAGE_FULL_FEATURE_PHASE)
log_warnx("received final login response with wrong NSG 0x%x",
login_nsg(response));
log_debugx("parameter negotiation done; "
"transitioning to Full Feature phase");
keys_delete(response_keys);
pdu_delete(response);
}
static void
login_send_chap_a(struct connection *conn)
{
struct pdu *request;
struct keys *request_keys;
request = login_new_request(conn);
request_keys = keys_new();
keys_add(request_keys, "CHAP_A", "5");
keys_save(request_keys, request);
keys_delete(request_keys);
pdu_send(request);
pdu_delete(request);
}
static void
login_send_chap_r(struct pdu *response)
{
struct connection *conn;
struct pdu *request;
struct keys *request_keys, *response_keys;
const char *chap_a, *chap_c, *chap_i;
char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH];
size_t challenge_len;
int error, rv;
unsigned char id;
char *mutual_chap_c, mutual_chap_i[4];
/*
* As in the rest of the initiator, 'request' means
* 'initiator -> target', and 'response' means 'target -> initiator',
*
* So, here the 'response' from the target is the packet that contains
* CHAP challenge; our CHAP response goes into 'request'.
*/
conn = response->pdu_connection;
response_keys = keys_new();
keys_load(response_keys, response);
/*
* First, compute the response.
*/
chap_a = keys_find(response_keys, "CHAP_A");
if (chap_a == NULL)
log_errx(1, "received CHAP packet without CHAP_A");
chap_c = keys_find(response_keys, "CHAP_C");
if (chap_c == NULL)
log_errx(1, "received CHAP packet without CHAP_C");
chap_i = keys_find(response_keys, "CHAP_I");
if (chap_i == NULL)
log_errx(1, "received CHAP packet without CHAP_I");
if (strcmp(chap_a, "5") != 0)
log_errx(1, "received CHAP packet "
"with unsupported CHAP_A \"%s\"", chap_a);
id = strtoul(chap_i, NULL, 10);
error = login_hex2bin(chap_c, &challenge, &challenge_len);
if (error != 0)
log_errx(1, "received CHAP packet with malformed CHAP_C");
login_compute_md5(id, conn->conn_conf.isc_secret,
challenge, challenge_len, response_bin, sizeof(response_bin));
free(challenge);
chap_r = login_bin2hex(response_bin, sizeof(response_bin));
keys_delete(response_keys);
request = login_new_request(conn);
request_keys = keys_new();
keys_add(request_keys, "CHAP_N", conn->conn_conf.isc_user);
keys_add(request_keys, "CHAP_R", chap_r);
free(chap_r);
/*
* If we want mutual authentication, we're expected to send
* our CHAP_I/CHAP_C now.
*/
if (conn->conn_conf.isc_mutual_user[0] != '\0') {
log_debugx("requesting mutual authentication; "
"binary challenge size is %zd bytes",
sizeof(conn->conn_mutual_challenge));
rv = RAND_bytes(conn->conn_mutual_challenge,
sizeof(conn->conn_mutual_challenge));
if (rv != 1) {
log_errx(1, "RAND_bytes failed: %s",
ERR_error_string(ERR_get_error(), NULL));
}
rv = RAND_bytes(&conn->conn_mutual_id,
sizeof(conn->conn_mutual_id));
if (rv != 1) {
log_errx(1, "RAND_bytes failed: %s",
ERR_error_string(ERR_get_error(), NULL));
}
mutual_chap_c = login_bin2hex(conn->conn_mutual_challenge,
sizeof(conn->conn_mutual_challenge));
snprintf(mutual_chap_i, sizeof(mutual_chap_i),
"%d", conn->conn_mutual_id);
keys_add(request_keys, "CHAP_I", mutual_chap_i);
keys_add(request_keys, "CHAP_C", mutual_chap_c);
free(mutual_chap_c);
}
keys_save(request_keys, request);
keys_delete(request_keys);
pdu_send(request);
pdu_delete(request);
}
static void
login_verify_mutual(const struct pdu *response)
{
struct connection *conn;
struct keys *response_keys;
const char *chap_n, *chap_r;
char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH];
size_t response_bin_len;
int error;
conn = response->pdu_connection;
response_keys = keys_new();
keys_load(response_keys, response);
chap_n = keys_find(response_keys, "CHAP_N");
if (chap_n == NULL)
log_errx(1, "received CHAP Response PDU without CHAP_N");
chap_r = keys_find(response_keys, "CHAP_R");
if (chap_r == NULL)
log_errx(1, "received CHAP Response PDU without CHAP_R");
error = login_hex2bin(chap_r, &response_bin, &response_bin_len);
if (error != 0)
log_errx(1, "received CHAP Response PDU with malformed CHAP_R");
if (strcmp(chap_n, conn->conn_conf.isc_mutual_user) != 0) {
fail(conn, "Mutual CHAP failed");
log_errx(1, "mutual CHAP authentication failed: wrong user");
}
login_compute_md5(conn->conn_mutual_id,
conn->conn_conf.isc_mutual_secret, conn->conn_mutual_challenge,
sizeof(conn->conn_mutual_challenge), expected_response_bin,
sizeof(expected_response_bin));
if (memcmp(response_bin, expected_response_bin,
sizeof(expected_response_bin)) != 0) {
fail(conn, "Mutual CHAP failed");
log_errx(1, "mutual CHAP authentication failed: wrong secret");
}
keys_delete(response_keys);
free(response_bin);
log_debugx("mutual CHAP authentication succeeded");
}
static void
login_chap(struct connection *conn)
{
struct pdu *response;
log_debugx("beginning CHAP authentication; sending CHAP_A");
login_send_chap_a(conn);
log_debugx("waiting for CHAP_A/CHAP_C/CHAP_I");
response = login_receive(conn, false);
log_debugx("sending CHAP_N/CHAP_R");
login_send_chap_r(response);
pdu_delete(response);
/*
* XXX: Make sure this is not susceptible to MITM.
*/
log_debugx("waiting for CHAP result");
response = login_receive(conn, false);
if (conn->conn_conf.isc_mutual_user[0] != '\0')
login_verify_mutual(response);
pdu_delete(response);
log_debugx("CHAP authentication done");
}
static void
login_create_isid(struct connection *conn)
{
int rv;
/*
* RFC 3720, 10.12.5: 10b, "Random" ISID.
*
*/
conn->conn_isid[0] = 0x80;
rv = RAND_bytes(&conn->conn_isid[1], 3);
if (rv != 1) {
log_errx(1, "RAND_bytes failed: %s",
ERR_error_string(ERR_get_error(), NULL));
}
}
void
login(struct connection *conn)
{
struct pdu *request, *response;
struct keys *request_keys, *response_keys;
struct iscsi_bhs_login_request *bhslr;
struct iscsi_bhs_login_response *bhslr2;
const char *auth_method;
int i;
login_create_isid(conn);
log_debugx("beginning Login phase; sending Login PDU");
request = login_new_request(conn);
bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
bhslr->bhslr_flags |= BHSLR_FLAGS_TRANSIT;
request_keys = keys_new();
if (conn->conn_conf.isc_user[0] == '\0')
keys_add(request_keys, "AuthMethod", "None");
else
keys_add(request_keys, "AuthMethod", "CHAP,None");
keys_add(request_keys, "InitiatorName",
conn->conn_conf.isc_initiator);
if (conn->conn_conf.isc_initiator_alias[0] != '\0') {
keys_add(request_keys, "InitiatorAlias",
conn->conn_conf.isc_initiator_alias);
}
if (conn->conn_conf.isc_discovery == 0) {
keys_add(request_keys, "SessionType", "Normal");
keys_add(request_keys,
"TargetName", conn->conn_conf.isc_target);
} else {
keys_add(request_keys, "SessionType", "Discovery");
}
keys_save(request_keys, request);
keys_delete(request_keys);
pdu_send(request);
pdu_delete(request);
response = login_receive(conn, true);
response_keys = keys_new();
keys_load(response_keys, response);
for (i = 0; i < KEYS_MAX; i++) {
if (response_keys->keys_names[i] == NULL)
break;
/*
* Not interested in AuthMethod at this point; we only need
* to parse things such as TargetAlias.
*
* XXX: This is somewhat ugly. We should have a way to apply
* all the keys to the session and use that by default
* instead of discarding them.
*/
if (strcmp(response_keys->keys_names[i], "AuthMethod") == 0)
continue;
login_negotiate_key(conn,
response_keys->keys_names[i], response_keys->keys_values[i]);
}
bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
if ((bhslr2->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0 &&
login_nsg(response) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) {
log_debugx("target requested transition "
"to operational negotiation");
keys_delete(response_keys);
pdu_delete(response);
login_negotiate(conn);
return;
}
auth_method = keys_find(response_keys, "AuthMethod");
if (auth_method == NULL)
log_errx(1, "received response without AuthMethod");
if (strcmp(auth_method, "None") == 0) {
log_debugx("target does not require authentication");
keys_delete(response_keys);
pdu_delete(response);
login_negotiate(conn);
return;
}
if (strcmp(auth_method, "CHAP") != 0) {
fail(conn, "Unsupported AuthMethod");
log_errx(1, "received response "
"with unsupported AuthMethod \"%s\"", auth_method);
}
if (conn->conn_conf.isc_user[0] == '\0' ||
conn->conn_conf.isc_secret[0] == '\0') {
fail(conn, "Authentication required");
log_errx(1, "target requests CHAP authentication, but we don't "
"have user and secret");
}
keys_delete(response_keys);
response_keys = NULL;
pdu_delete(response);
response = NULL;
login_chap(conn);
login_negotiate(conn);
}

281
usr.sbin/iscsid/pdu.c Normal file
View File

@ -0,0 +1,281 @@
/*-
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/types.h>
#include <sys/uio.h>
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "iscsid.h"
#include "iscsi_proto.h"
#ifdef ICL_KERNEL_PROXY
#include <sys/ioctl.h>
#endif
static int
pdu_ahs_length(const struct pdu *pdu)
{
return (pdu->pdu_bhs->bhs_total_ahs_len * 4);
}
static int
pdu_data_segment_length(const struct pdu *pdu)
{
uint32_t len = 0;
len += pdu->pdu_bhs->bhs_data_segment_len[0];
len <<= 8;
len += pdu->pdu_bhs->bhs_data_segment_len[1];
len <<= 8;
len += pdu->pdu_bhs->bhs_data_segment_len[2];
return (len);
}
static void
pdu_set_data_segment_length(struct pdu *pdu, uint32_t len)
{
pdu->pdu_bhs->bhs_data_segment_len[2] = len;
pdu->pdu_bhs->bhs_data_segment_len[1] = len >> 8;
pdu->pdu_bhs->bhs_data_segment_len[0] = len >> 16;
}
struct pdu *
pdu_new(struct connection *conn)
{
struct pdu *pdu;
pdu = calloc(sizeof(*pdu), 1);
if (pdu == NULL)
log_err(1, "calloc");
pdu->pdu_bhs = calloc(sizeof(*pdu->pdu_bhs), 1);
if (pdu->pdu_bhs == NULL)
log_err(1, "calloc");
pdu->pdu_connection = conn;
return (pdu);
}
struct pdu *
pdu_new_response(struct pdu *request)
{
return (pdu_new(request->pdu_connection));
}
#ifdef ICL_KERNEL_PROXY
void
pdu_receive(struct pdu *pdu)
{
struct iscsi_daemon_receive *idr;
size_t len;
int error;
pdu->pdu_data = malloc(ISCSI_MAX_DATA_SEGMENT_LENGTH);
if (pdu->pdu_data == NULL)
log_err(1, "malloc");
idr = calloc(1, sizeof(*idr));
if (idr == NULL)
log_err(1, "calloc");
idr->idr_session_id = pdu->pdu_connection->conn_session_id;
idr->idr_bhs = pdu->pdu_bhs;
idr->idr_data_segment_len = ISCSI_MAX_DATA_SEGMENT_LENGTH;
idr->idr_data_segment = pdu->pdu_data;
error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDRECEIVE, idr);
if (error != 0)
log_err(1, "ISCSIDRECEIVE");
len = pdu_ahs_length(pdu);
if (len > 0)
log_errx(1, "protocol error: non-empty AHS");
len = pdu_data_segment_length(pdu);
assert(len <= ISCSI_MAX_DATA_SEGMENT_LENGTH);
pdu->pdu_data_len = len;
free(idr);
}
void
pdu_send(struct pdu *pdu)
{
struct iscsi_daemon_send *ids;
int error;
pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
ids = calloc(1, sizeof(*ids));
if (ids == NULL)
log_err(1, "calloc");
ids->ids_session_id = pdu->pdu_connection->conn_session_id;
ids->ids_bhs = pdu->pdu_bhs;
ids->ids_data_segment_len = pdu->pdu_data_len;
ids->ids_data_segment = pdu->pdu_data;
error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDSEND, ids);
if (error != 0)
log_err(1, "ISCSIDSEND");
free(ids);
}
#else /* !ICL_KERNEL_PROXY */
static size_t
pdu_padding(const struct pdu *pdu)
{
if ((pdu->pdu_data_len % 4) != 0)
return (4 - (pdu->pdu_data_len % 4));
return (0);
}
static void
pdu_read(int fd, char *data, size_t len)
{
ssize_t ret;
while (len > 0) {
ret = read(fd, data, len);
if (ret < 0) {
if (timed_out())
log_errx(1, "exiting due to timeout");
log_err(1, "read");
} else if (ret == 0)
log_errx(1, "read: connection lost");
len -= ret;
data += ret;
}
}
void
pdu_receive(struct pdu *pdu)
{
size_t len, padding;
char dummy[4];
pdu_read(pdu->pdu_connection->conn_socket,
(char *)pdu->pdu_bhs, sizeof(*pdu->pdu_bhs));
len = pdu_ahs_length(pdu);
if (len > 0)
log_errx(1, "protocol error: non-empty AHS");
len = pdu_data_segment_length(pdu);
if (len > 0) {
if (len > ISCSI_MAX_DATA_SEGMENT_LENGTH) {
log_errx(1, "protocol error: received PDU "
"with DataSegmentLength exceeding %d",
ISCSI_MAX_DATA_SEGMENT_LENGTH);
}
pdu->pdu_data_len = len;
pdu->pdu_data = malloc(len);
if (pdu->pdu_data == NULL)
log_err(1, "malloc");
pdu_read(pdu->pdu_connection->conn_socket,
(char *)pdu->pdu_data, pdu->pdu_data_len);
padding = pdu_padding(pdu);
if (padding != 0) {
assert(padding < sizeof(dummy));
pdu_read(pdu->pdu_connection->conn_socket,
(char *)dummy, padding);
}
}
}
void
pdu_send(struct pdu *pdu)
{
ssize_t ret, total_len;
size_t padding;
uint32_t zero = 0;
struct iovec iov[3];
int iovcnt;
pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
iov[0].iov_base = pdu->pdu_bhs;
iov[0].iov_len = sizeof(*pdu->pdu_bhs);
total_len = iov[0].iov_len;
iovcnt = 1;
if (pdu->pdu_data_len > 0) {
iov[1].iov_base = pdu->pdu_data;
iov[1].iov_len = pdu->pdu_data_len;
total_len += iov[1].iov_len;
iovcnt = 2;
padding = pdu_padding(pdu);
if (padding > 0) {
assert(padding < sizeof(zero));
iov[2].iov_base = &zero;
iov[2].iov_len = padding;
total_len += iov[2].iov_len;
iovcnt = 3;
}
}
ret = writev(pdu->pdu_connection->conn_socket, iov, iovcnt);
if (ret < 0) {
if (timed_out())
log_errx(1, "exiting due to timeout");
log_err(1, "writev");
}
if (ret != total_len)
log_errx(1, "short write");
}
#endif /* !ICL_KERNEL_PROXY */
void
pdu_delete(struct pdu *pdu)
{
free(pdu->pdu_data);
free(pdu->pdu_bhs);
free(pdu);
}