mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-16 18:21:27 +00:00
Add the opportunity to globally specify multiple addresses, which iSCSI/iSER portal will listen on. There is a way to specify the addresses for a target, on which portals it will be available, using the allowed_portal parameter. There is also a way to set this globally via the iscsi-scstd --address parameter. The problem is that the last option allows you to specify only one local address. So extend this functionality to specify more then one address. Also increase the maximum number of listening addresses to 32. Signed-off-by: Aleksandr Dyadyushkin <alextalker@ya.ru>
1036 lines
24 KiB
C
1036 lines
24 KiB
C
/*
|
|
* Copyright (C) 2002 - 2003 Ardis Technologies <roman@ardistech.com>
|
|
* Copyright (C) 2007 - 2018 Vladislav Bolkhovitin
|
|
* Copyright (C) 2007 - 2018 Western Digital Corporation
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation, version 2
|
|
* of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <getopt.h>
|
|
#include <netdb.h>
|
|
#include <signal.h>
|
|
|
|
#include <poll.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/un.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <netinet/ip.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include "iscsid.h"
|
|
#include "iscsi_adm.h"
|
|
|
|
static char *server_addresses[ADDR_MAX];
|
|
uint16_t server_port = ISCSI_LISTEN_PORT;
|
|
|
|
struct pollfd poll_array[POLL_MAX];
|
|
|
|
static struct connection *incoming[INCOMING_MAX];
|
|
static int incoming_cnt;
|
|
int ctrl_fd, ipc_fd, nl_fd;
|
|
int conn_blocked;
|
|
|
|
struct iscsi_init_params iscsi_init_params;
|
|
|
|
static const char program_name[] = "iscsi-scstd";
|
|
|
|
static struct option const long_options[] = {
|
|
{"config", required_argument, 0, 'c'},
|
|
{"foreground", no_argument, 0, 'f'},
|
|
{"debug", required_argument, 0, 'd'},
|
|
{"uid", required_argument, 0, 'u'},
|
|
{"gid", required_argument, 0, 'g'},
|
|
{"address", required_argument, 0, 'a'},
|
|
{"port", required_argument, 0, 'p'},
|
|
{"version", no_argument, 0, 'v'},
|
|
{"help", no_argument, 0, 'h'},
|
|
{0, 0, 0, 0},
|
|
};
|
|
|
|
int init_report_pipe[2];
|
|
|
|
static void usage(int status)
|
|
{
|
|
if (status != 0)
|
|
fprintf(stderr, "Try `%s --help' for more information.\n", program_name);
|
|
else {
|
|
printf("Usage: %s [OPTION]\n", program_name);
|
|
printf("\
|
|
iSCSI target daemon.\n\
|
|
-c, --config=[path] Execute in the config file.\n");
|
|
printf("\
|
|
-f, --foreground make the program run in the foreground\n\
|
|
-d, --debug debuglevel print debugging information\n\
|
|
-u, --uid=uid run as uid, default is current user\n\
|
|
-g, --gid=gid run as gid, default is current user group\n\
|
|
-a, --address=address ... listen on specified space-separated list of local address instead of all\n\
|
|
-p, --port=port listen on specified port instead of 3260\n\
|
|
-h, --help display this help and exit\n\
|
|
");
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
const char *get_error_str(int error)
|
|
{
|
|
if (error == EAI_SYSTEM)
|
|
return strerror(errno);
|
|
else
|
|
return gai_strerror(error);
|
|
}
|
|
|
|
static void create_listen_socket(struct pollfd *array)
|
|
{
|
|
struct addrinfo hints, *res, *res0;
|
|
char servname[64];
|
|
int i, k, sock, opt, rc;
|
|
|
|
memset(servname, 0, sizeof(servname));
|
|
snprintf(servname, sizeof(servname), "%d", server_port);
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_flags = AI_PASSIVE;
|
|
|
|
i = 0;
|
|
for (k = 0; k < ADDR_MAX; k++) {
|
|
char *server_address;
|
|
|
|
server_address = server_addresses[k];
|
|
if (k > 0 && server_address == NULL)
|
|
break;
|
|
|
|
if (i == LISTEN_MAX) {
|
|
log_error("Cannot handle address %s! Too many were specified.", server_address);
|
|
exit(1);
|
|
}
|
|
|
|
rc = getaddrinfo(server_address, servname, &hints, &res0);
|
|
if (rc != 0) {
|
|
log_error("Unable to get address info [%s] (%s)!",
|
|
server_address, get_error_str(rc));
|
|
exit(1);
|
|
}
|
|
|
|
for (res = res0; res && i < LISTEN_MAX; res = res->ai_next) {
|
|
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
|
if (sock < 0) {
|
|
log_error("Unable to create server socket (%s) %d %d %d!",
|
|
strerror(errno), res->ai_family,
|
|
res->ai_socktype, res->ai_protocol);
|
|
continue;
|
|
}
|
|
|
|
sock_set_keepalive(sock, 50);
|
|
|
|
opt = 1;
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
|
|
log_warning("Unable to set SO_REUSEADDR on server socket (%s)!",
|
|
strerror(errno));
|
|
opt = 1;
|
|
if (res->ai_family == AF_INET6 &&
|
|
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt))) {
|
|
log_error("Unable to restrict IPv6 socket (%s)", strerror(errno));
|
|
close(sock);
|
|
continue;
|
|
}
|
|
|
|
if (bind(sock, res->ai_addr, res->ai_addrlen)) {
|
|
log_error("Unable to bind server socket (%s)!", strerror(errno));
|
|
close(sock);
|
|
continue;
|
|
}
|
|
|
|
if (listen(sock, INCOMING_MAX)) {
|
|
log_error("Unable to listen to server socket (%s)!", strerror(errno));
|
|
close(sock);
|
|
continue;
|
|
}
|
|
|
|
set_non_blocking(sock);
|
|
|
|
array[i].fd = sock;
|
|
array[i].events = POLLIN;
|
|
|
|
i++;
|
|
}
|
|
|
|
if (res)
|
|
log_error("Unable to listen on all available sockets.");
|
|
|
|
freeaddrinfo(res0);
|
|
}
|
|
|
|
if (i == 0)
|
|
exit(1);
|
|
}
|
|
|
|
static struct connection *alloc_and_init_conn(int fd)
|
|
{
|
|
struct pollfd *pollfd;
|
|
struct connection *conn = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < INCOMING_MAX; i++) {
|
|
if (!incoming[i])
|
|
break;
|
|
}
|
|
if (i >= INCOMING_MAX) {
|
|
log_error("Unable to find incoming slot? %d\n", i);
|
|
goto out;
|
|
}
|
|
|
|
conn = conn_alloc();
|
|
if (!conn) {
|
|
log_error("Fail to allocate %s", "conn\n");
|
|
goto out;
|
|
}
|
|
|
|
conn->fd = fd;
|
|
incoming[i] = conn;
|
|
|
|
pollfd = &poll_array[POLL_INCOMING + i];
|
|
pollfd->fd = fd;
|
|
pollfd->events = POLLIN;
|
|
pollfd->revents = 0;
|
|
|
|
conn_read_pdu(conn);
|
|
set_non_blocking(fd);
|
|
|
|
out:
|
|
return conn;
|
|
}
|
|
|
|
static int transmit_iser(int fd, bool start)
|
|
{
|
|
int opt = start;
|
|
|
|
return ioctl(fd, RDMA_CORK, &opt, sizeof(opt));
|
|
}
|
|
|
|
static int cork_transmit_iser(int fd)
|
|
{
|
|
return transmit_iser(fd, true);
|
|
}
|
|
|
|
static int uncork_transmit_iser(int fd)
|
|
{
|
|
return transmit_iser(fd, false);
|
|
}
|
|
|
|
static void create_iser_listen_socket(struct pollfd *array)
|
|
{
|
|
struct addrinfo hints, *res, *res0;
|
|
char servname[64];
|
|
char *server_address;
|
|
int rc, i, k;
|
|
int iser_fd;
|
|
struct isert_addr_info info;
|
|
|
|
iser_fd = create_and_open_dev("isert_scst", 1);
|
|
|
|
if (iser_fd >= 0) {
|
|
poll_array[POLL_ISER_LISTEN].fd = iser_fd;
|
|
poll_array[POLL_ISER_LISTEN].events = POLLIN;
|
|
|
|
/* RDMAExtensions */
|
|
session_keys[key_rdma_extensions].max = 1;
|
|
session_keys[key_rdma_extensions].local_def = 1;
|
|
} else {
|
|
poll_array[POLL_ISER_LISTEN].fd = -1;
|
|
poll_array[POLL_ISER_LISTEN].events = 0;
|
|
return;
|
|
}
|
|
|
|
memset(servname, 0, sizeof(servname));
|
|
snprintf(servname, sizeof(servname), "%d", server_port);
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_flags = AI_PASSIVE;
|
|
|
|
i = 0;
|
|
for (k = 0; k < ADDR_MAX; k++) {
|
|
server_address = server_addresses[k];
|
|
|
|
if (k > 0 && server_address == NULL)
|
|
break;
|
|
|
|
if (i == ISERT_MAX_PORTALS) {
|
|
log_error("iSER: Cannot handle address %s! Too many were specified.", server_address);
|
|
exit(1);
|
|
}
|
|
|
|
rc = getaddrinfo(server_address, servname, &hints, &res0);
|
|
if (rc != 0) {
|
|
log_error("iSER: Unable to get address info[%s] (%s)!",
|
|
server_address, get_error_str(rc));
|
|
exit(1);
|
|
}
|
|
|
|
for (res = res0; res && i < ISERT_MAX_PORTALS; res = res->ai_next) {
|
|
memcpy(&info.addr, res->ai_addr, res->ai_addrlen);
|
|
info.addr_len = res->ai_addrlen;
|
|
|
|
rc = ioctl(iser_fd, SET_LISTEN_ADDR, &info);
|
|
if (rc != 0) {
|
|
log_error("iSER: Unable to set listen address (%s)!",
|
|
strerror(errno));
|
|
}
|
|
++i;
|
|
}
|
|
|
|
if (res)
|
|
log_error("iSER: Unable to listen on all available sockets.");
|
|
|
|
freeaddrinfo(res0);
|
|
}
|
|
}
|
|
|
|
static int iser_getsockname(int fd, struct sockaddr *name, socklen_t *namelen)
|
|
{
|
|
struct isert_addr_info addr;
|
|
int ret;
|
|
|
|
ret = ioctl(fd, GET_PORTAL_ADDR, &addr, sizeof(addr));
|
|
if (ret)
|
|
return ret;
|
|
|
|
memcpy(name, &addr.addr, addr.addr_len);
|
|
*namelen = addr.addr_len;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int iser_is_discovery(int fd)
|
|
{
|
|
int val = 1;
|
|
|
|
return ioctl(fd, DISCOVERY_SESSION, &val, sizeof(val));
|
|
}
|
|
|
|
static void iser_accept(int fd)
|
|
{
|
|
char buff[256];
|
|
int ret, conn_fd;
|
|
struct connection *conn;
|
|
char target_portal[ISCSI_PORTAL_LEN], target_portal_port[NI_MAXSERV];
|
|
struct isert_addr_info addr;
|
|
|
|
ret = read(fd, buff, sizeof(buff));
|
|
if (ret == -1)
|
|
goto out;
|
|
|
|
conn_fd = open(buff, O_RDWR);
|
|
if (conn_fd == -1) {
|
|
log_error("open(iser_connection) %s failed: %s\n",
|
|
buff, strerror(errno));
|
|
goto out;
|
|
}
|
|
|
|
ret = ioctl(conn_fd, GET_PORTAL_ADDR, &addr, sizeof(addr));
|
|
if (ret) {
|
|
log_error("ioctl(GET_PORTAL_ADDR) failed: %s\n",
|
|
strerror(errno));
|
|
goto out_close;
|
|
}
|
|
|
|
ret = getnameinfo((struct sockaddr *)&addr, sizeof(addr), target_portal,
|
|
sizeof(target_portal), target_portal_port,
|
|
sizeof(target_portal_port),
|
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
|
if (ret != 0) {
|
|
log_error("Target portal getnameinfo() failed: %s!",
|
|
get_error_str(ret));
|
|
goto out_close;
|
|
}
|
|
|
|
log_info("iSER Connect to %s:%s", target_portal, target_portal_port);
|
|
|
|
if (conn_blocked) {
|
|
log_warning("Connection refused due to blocking\n");
|
|
goto out_close;
|
|
}
|
|
|
|
conn = alloc_and_init_conn(conn_fd);
|
|
if (!conn)
|
|
goto out_close;
|
|
|
|
conn->target_portal = strdup(target_portal);
|
|
if (conn->target_portal == NULL) {
|
|
log_error("Unable to duplicate target portal %s", target_portal);
|
|
goto out_free;
|
|
}
|
|
|
|
conn->cork_transmit = cork_transmit_iser;
|
|
conn->uncork_transmit = uncork_transmit_iser;
|
|
conn->getsockname = iser_getsockname;
|
|
conn->is_discovery = iser_is_discovery;
|
|
conn->is_iser = true;
|
|
incoming_cnt++;
|
|
|
|
out:
|
|
return;
|
|
|
|
out_free:
|
|
conn_free(conn);
|
|
|
|
out_close:
|
|
close(conn_fd);
|
|
goto out;
|
|
}
|
|
|
|
static int transmit_sock(int fd, bool start)
|
|
{
|
|
int opt = start;
|
|
|
|
return setsockopt(fd, SOL_TCP, TCP_CORK, &opt, sizeof(opt));
|
|
}
|
|
|
|
static int cork_transmit_sock(int fd)
|
|
{
|
|
return transmit_sock(fd, true);
|
|
}
|
|
|
|
static int uncork_transmit_sock(int fd)
|
|
{
|
|
return transmit_sock(fd, false);
|
|
}
|
|
|
|
static int tcp_is_discovery(int fd)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void accept_connection(int listen)
|
|
{
|
|
union {
|
|
struct sockaddr sa;
|
|
struct sockaddr_in sin;
|
|
struct sockaddr_in6 sin6;
|
|
} from, to;
|
|
socklen_t namesize;
|
|
struct connection *conn;
|
|
int fd, rc;
|
|
char initiator_addr[ISCSI_PORTAL_LEN], initiator_port[NI_MAXSERV];
|
|
char target_portal[ISCSI_PORTAL_LEN], target_portal_port[NI_MAXSERV];
|
|
|
|
namesize = sizeof(from);
|
|
if ((fd = accept(listen, &from.sa, &namesize)) < 0) {
|
|
switch (errno) {
|
|
case EINTR:
|
|
case EAGAIN:
|
|
case ENETDOWN:
|
|
case EPROTO:
|
|
case ENOPROTOOPT:
|
|
case EHOSTDOWN:
|
|
case ENONET:
|
|
case EHOSTUNREACH:
|
|
case EOPNOTSUPP:
|
|
case ENETUNREACH:
|
|
break;
|
|
default:
|
|
log_error("accept(incoming_socket) failed: %s",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
namesize = sizeof(to);
|
|
rc = getsockname(fd, &to.sa, &namesize);
|
|
if (rc != 0) {
|
|
log_error("getsockname() failed: %s", strerror(errno));
|
|
goto out_close;
|
|
}
|
|
|
|
rc = getnameinfo(&to.sa, sizeof(to), target_portal, sizeof(target_portal),
|
|
target_portal_port, sizeof(target_portal_port),
|
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
|
if (rc != 0) {
|
|
log_error("Target portal getnameinfo() failed: %s!",
|
|
get_error_str(rc));
|
|
goto out_close;
|
|
}
|
|
|
|
rc = getnameinfo(&from.sa, sizeof(from), initiator_addr,
|
|
sizeof(initiator_addr), initiator_port,
|
|
sizeof(initiator_port), NI_NUMERICHOST | NI_NUMERICSERV);
|
|
if (rc != 0) {
|
|
log_error("Initiator getnameinfo() failed: %s!",
|
|
get_error_str(rc));
|
|
goto out_close;
|
|
}
|
|
|
|
log_info("Connect from %s:%s to %s:%s", initiator_addr, initiator_port,
|
|
target_portal, target_portal_port);
|
|
|
|
if (conn_blocked) {
|
|
log_warning("Connection refused due to blocking\n");
|
|
goto out_close;
|
|
}
|
|
|
|
conn = alloc_and_init_conn(fd);
|
|
if (!conn)
|
|
goto out_close;
|
|
|
|
conn->target_portal = strdup(target_portal);
|
|
if (conn->target_portal == NULL) {
|
|
log_error("Unable to duplicate target portal %s", target_portal);
|
|
goto out_free;
|
|
}
|
|
|
|
conn->cork_transmit = cork_transmit_sock;
|
|
conn->uncork_transmit = uncork_transmit_sock;
|
|
conn->getsockname = getsockname;
|
|
conn->is_discovery = tcp_is_discovery;
|
|
conn_read_pdu(conn);
|
|
|
|
incoming_cnt++;
|
|
|
|
out:
|
|
return;
|
|
|
|
out_free:
|
|
conn_free(conn);
|
|
|
|
out_close:
|
|
close(fd);
|
|
goto out;
|
|
}
|
|
|
|
static void __set_fd(int idx, int fd)
|
|
{
|
|
poll_array[idx].fd = fd;
|
|
poll_array[idx].events = fd ? POLLIN : 0;
|
|
}
|
|
|
|
void isns_set_fd(int isns, int scn_listen, int scn)
|
|
{
|
|
__set_fd(POLL_ISNS, isns);
|
|
__set_fd(POLL_SCN_LISTEN, scn_listen);
|
|
__set_fd(POLL_SCN, scn);
|
|
}
|
|
|
|
static void event_conn(struct connection *conn, struct pollfd *pollfd)
|
|
{
|
|
int res;
|
|
|
|
again:
|
|
switch (conn->iostate) {
|
|
case IOSTATE_READ_BHS:
|
|
case IOSTATE_READ_AHS_DATA:
|
|
read_again:
|
|
errno = 0; /* for the log_debug() */
|
|
res = read(pollfd->fd, conn->buffer, conn->rwsize);
|
|
if (res <= 0) {
|
|
log_debug(1, "read(%u, %p, %u) returned %d, errno=%u",
|
|
pollfd->fd, conn->buffer, conn->rwsize, res, errno);
|
|
if (res == 0 || (errno != EINTR && errno != EAGAIN)) {
|
|
conn->state = STATE_DROP;
|
|
goto out;
|
|
} else if (errno == EINTR)
|
|
goto read_again;
|
|
break;
|
|
}
|
|
conn->rwsize -= res;
|
|
conn->buffer += res;
|
|
if (conn->rwsize)
|
|
break;
|
|
|
|
switch (conn->iostate) {
|
|
case IOSTATE_READ_BHS:
|
|
conn->iostate = IOSTATE_READ_AHS_DATA;
|
|
conn->req.ahssize = conn->req.bhs.ahslength * 4;
|
|
conn->req.datasize = ((conn->req.bhs.datalength[0] << 16) +
|
|
(conn->req.bhs.datalength[1] << 8) +
|
|
conn->req.bhs.datalength[2]);
|
|
conn->rwsize = (conn->req.ahssize + conn->req.datasize + 3) & -4;
|
|
if (conn->rwsize > INCOMING_BUFSIZE) {
|
|
log_warning("Recv PDU with invalid size %d "
|
|
"(max: %d)", conn->rwsize,
|
|
INCOMING_BUFSIZE);
|
|
conn->state = STATE_DROP;
|
|
goto out;
|
|
}
|
|
if (conn->rwsize) {
|
|
if (!conn->req_buffer) {
|
|
conn->req_buffer = malloc(INCOMING_BUFSIZE);
|
|
if (!conn->req_buffer) {
|
|
log_error("Failed to alloc recv buffer");
|
|
conn->state = STATE_DROP;
|
|
goto out;
|
|
}
|
|
}
|
|
conn->buffer = conn->req_buffer;
|
|
conn->req.ahs = conn->buffer;
|
|
conn->req.data = conn->buffer + conn->req.ahssize;
|
|
goto read_again;
|
|
}
|
|
/* fall-through */
|
|
|
|
case IOSTATE_READ_AHS_DATA:
|
|
conn_write_pdu(conn);
|
|
pollfd->events = POLLOUT;
|
|
|
|
log_pdu(2, &conn->req);
|
|
if (!cmnd_execute(conn))
|
|
conn->state = STATE_EXIT;
|
|
|
|
if (conn->state == STATE_EXIT) {
|
|
/* We need to send response */
|
|
goto again;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case IOSTATE_WRITE_BHS:
|
|
case IOSTATE_WRITE_AHS:
|
|
case IOSTATE_WRITE_DATA:
|
|
write_again:
|
|
conn->cork_transmit(pollfd->fd);
|
|
res = write(pollfd->fd, conn->buffer, conn->rwsize);
|
|
if (res < 0) {
|
|
log_debug(1, "write(%u, %p, %u) returned %d, errno=%u",
|
|
pollfd->fd, conn->buffer, conn->rwsize, res, errno);
|
|
if (errno != EINTR && errno != EAGAIN) {
|
|
conn->state = STATE_DROP;
|
|
goto out;
|
|
} else if (errno == EINTR)
|
|
goto write_again;
|
|
break;
|
|
}
|
|
|
|
conn->rwsize -= res;
|
|
conn->buffer += res;
|
|
if (conn->rwsize)
|
|
goto write_again;
|
|
|
|
switch (conn->iostate) {
|
|
case IOSTATE_WRITE_BHS:
|
|
if (conn->rsp.ahssize) {
|
|
conn->iostate = IOSTATE_WRITE_AHS;
|
|
conn->buffer = conn->rsp.ahs;
|
|
conn->rwsize = conn->rsp.ahssize;
|
|
goto write_again;
|
|
}
|
|
/* fall-through */
|
|
case IOSTATE_WRITE_AHS:
|
|
if (conn->rsp.datasize) {
|
|
int o;
|
|
|
|
conn->iostate = IOSTATE_WRITE_DATA;
|
|
conn->buffer = conn->rsp.data;
|
|
conn->rwsize = conn->rsp.datasize;
|
|
o = conn->rwsize & 3;
|
|
if (o) {
|
|
for (o = 4 - o; o; o--)
|
|
*((u8 *) conn->buffer + conn->rwsize++) =
|
|
0;
|
|
}
|
|
goto write_again;
|
|
}
|
|
/* fall-through */
|
|
case IOSTATE_WRITE_DATA:
|
|
conn->uncork_transmit(pollfd->fd);
|
|
cmnd_finish(conn);
|
|
|
|
switch (conn->state) {
|
|
case STATE_KERNEL:
|
|
conn_pass_to_kern(conn, pollfd->fd);
|
|
if (conn->passed_to_kern)
|
|
conn->state = STATE_CLOSE;
|
|
else
|
|
conn->state = STATE_EXIT;
|
|
break;
|
|
case STATE_EXIT:
|
|
case STATE_CLOSE:
|
|
case STATE_DROP:
|
|
break;
|
|
default:
|
|
conn_read_pdu(conn);
|
|
pollfd->events = POLLIN;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
log_error("illegal iostate %d for port %d!\n", conn->iostate,
|
|
pollfd->fd);
|
|
exit(1);
|
|
}
|
|
out:
|
|
return;
|
|
}
|
|
|
|
static void event_loop(void)
|
|
{
|
|
int res, i;
|
|
|
|
create_listen_socket(poll_array + POLL_LISTEN);
|
|
create_iser_listen_socket(poll_array);
|
|
|
|
poll_array[POLL_IPC].fd = ipc_fd;
|
|
poll_array[POLL_IPC].events = POLLIN;
|
|
poll_array[POLL_NL].fd = nl_fd;
|
|
poll_array[POLL_NL].events = POLLIN;
|
|
|
|
for (i = 0; i < INCOMING_MAX; i++) {
|
|
poll_array[POLL_INCOMING + i].fd = -1;
|
|
poll_array[POLL_INCOMING + i].events = 0;
|
|
incoming[i] = NULL;
|
|
}
|
|
|
|
close(init_report_pipe[0]);
|
|
res = 0;
|
|
|
|
if (log_daemon)
|
|
res = write(init_report_pipe[1], &res, sizeof(res));
|
|
|
|
close(init_report_pipe[1]);
|
|
|
|
while (1) {
|
|
if (!iscsi_enabled) {
|
|
handle_iscsi_events(nl_fd, true);
|
|
continue;
|
|
}
|
|
res = poll(poll_array, POLL_MAX, isns_timeout);
|
|
if (res == 0) {
|
|
isns_handle(1);
|
|
continue;
|
|
} else if (res < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
else if (errno == EINVAL)
|
|
log_error("%s: poll() failed with EINVAL. Should "
|
|
"you increase RLIMIT_NOFILE (ulimit -n)? "
|
|
"Or upgrade your kernel? Kernels below 2.6.19 "
|
|
"have a bug, which doesn't allow poll() "
|
|
"to work with >256 file descriptors. See "
|
|
"http://sourceforge.net/mailarchive/forum.php?"
|
|
"thread_name=9392A06CB0FDC847B3A530B3DC174E7B0"
|
|
"55F1EF3%40mse10be1.mse10.exchange.ms&forum_"
|
|
"name=scst-devel for more details. Alternatively, "
|
|
"you can decrease iscsi_scstd.c::INCOMING_MAX "
|
|
"constant to a lower value, e.g. 128, then "
|
|
"recompile and reinstall the user space part "
|
|
"of iSCSI-SCST.", __func__);
|
|
else
|
|
log_error("%s: poll() failed: %s", __func__,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
for (i = 0; i < LISTEN_MAX; i++) {
|
|
if (poll_array[POLL_LISTEN + i].revents
|
|
&& incoming_cnt < INCOMING_MAX)
|
|
accept_connection(poll_array[POLL_LISTEN + i].fd);
|
|
}
|
|
|
|
if (poll_array[POLL_NL].revents)
|
|
handle_iscsi_events(nl_fd, false);
|
|
|
|
if (poll_array[POLL_IPC].revents)
|
|
iscsi_adm_request_handle(ipc_fd);
|
|
|
|
if (poll_array[POLL_ISNS].revents)
|
|
isns_handle(0);
|
|
|
|
if (poll_array[POLL_SCN_LISTEN].revents)
|
|
isns_scn_handle(1);
|
|
|
|
if (poll_array[POLL_SCN].revents)
|
|
isns_scn_handle(0);
|
|
|
|
if (poll_array[POLL_ISER_LISTEN].revents)
|
|
iser_accept(poll_array[POLL_ISER_LISTEN].fd);
|
|
|
|
for (i = 0; i < INCOMING_MAX; i++) {
|
|
struct connection *conn = incoming[i];
|
|
struct pollfd *pollfd = &poll_array[POLL_INCOMING + i];
|
|
|
|
if (!conn || !pollfd->revents)
|
|
continue;
|
|
|
|
pollfd->revents = 0;
|
|
|
|
event_conn(conn, pollfd);
|
|
|
|
if ((conn->state == STATE_CLOSE) ||
|
|
(conn->state == STATE_EXIT) ||
|
|
(conn->state == STATE_DROP)) {
|
|
struct session *sess = conn->sess;
|
|
|
|
log_debug(1, "closing conn %p state=0x%x fd=%u",
|
|
conn, conn->state, pollfd->fd);
|
|
conn_free_pdu(conn);
|
|
close(pollfd->fd);
|
|
pollfd->fd = -1;
|
|
incoming[i] = NULL;
|
|
incoming_cnt--;
|
|
if (conn->state != STATE_CLOSE) {
|
|
if (conn->passed_to_kern) {
|
|
kernel_conn_destroy(conn->tid,
|
|
conn->sess->sid.id64,
|
|
conn->cid);
|
|
} else {
|
|
/*
|
|
* Check if session could not be established,
|
|
* but sessions count was already incremented
|
|
*/
|
|
if (!sess && conn->sessions_count_incremented)
|
|
conn->target->sessions_count--;
|
|
log_debug(1, "conn %p freed (sess %p, empty %d)",
|
|
conn, sess,
|
|
sess ? list_empty(&sess->conn_list) : -1);
|
|
conn_free(conn);
|
|
if (sess && list_empty(&sess->conn_list))
|
|
session_free(sess);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void init_max_params(void)
|
|
{
|
|
if ((session_keys[key_max_recv_data_length].local_def != -1) ||
|
|
(session_keys[key_max_recv_data_length].max != -1) ||
|
|
(session_keys[key_max_xmit_data_length].local_def != -1) ||
|
|
(session_keys[key_max_xmit_data_length].max != -1) ||
|
|
(session_keys[key_max_burst_length].local_def != -1) ||
|
|
(session_keys[key_max_burst_length].max != -1) ||
|
|
(session_keys[key_first_burst_length].max != -1)) {
|
|
log_error("Wrong session_keys initialization");
|
|
exit(-1);
|
|
}
|
|
|
|
/* QueuedCommands */
|
|
target_keys[key_queued_cmnds].local_def = min((int)target_keys[key_queued_cmnds].local_def,
|
|
iscsi_init_params.max_queued_cmds);
|
|
target_keys[key_queued_cmnds].max = min((int)target_keys[key_queued_cmnds].max,
|
|
iscsi_init_params.max_queued_cmds);
|
|
target_keys[key_queued_cmnds].min = min((int)target_keys[key_queued_cmnds].min,
|
|
iscsi_init_params.max_queued_cmds);
|
|
|
|
/* MaxRecvDataSegmentLength */
|
|
session_keys[key_max_recv_data_length].local_def = iscsi_init_params.max_data_seg_len;
|
|
session_keys[key_max_recv_data_length].max = iscsi_init_params.max_data_seg_len;
|
|
|
|
/* MaxXmitDataSegmentLength */
|
|
session_keys[key_max_xmit_data_length].local_def = iscsi_init_params.max_data_seg_len;
|
|
session_keys[key_max_xmit_data_length].max = iscsi_init_params.max_data_seg_len;
|
|
|
|
/* MaxBurstLength */
|
|
session_keys[key_max_burst_length].local_def = iscsi_init_params.max_data_seg_len;
|
|
session_keys[key_max_burst_length].max = iscsi_init_params.max_data_seg_len;
|
|
|
|
/* FirstBurstLength */
|
|
session_keys[key_first_burst_length].local_def =
|
|
min((int)session_keys[key_first_burst_length].local_def,
|
|
iscsi_init_params.max_data_seg_len);
|
|
session_keys[key_first_burst_length].max = iscsi_init_params.max_data_seg_len;
|
|
|
|
return;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int ch, longindex;
|
|
char *config = NULL;
|
|
uid_t uid = 0;
|
|
gid_t gid = 0;
|
|
int err;
|
|
|
|
|
|
if (pipe(init_report_pipe) == -1) {
|
|
perror("pipe failed");
|
|
exit(-1);
|
|
}
|
|
|
|
/*
|
|
* Otherwise we could die in some later write() during the event_loop()
|
|
* instead of getting EPIPE!
|
|
*
|
|
* The effects of signal(2) in a multithreaded process are unspecified,
|
|
* so use sigaction(2) instead.
|
|
*/
|
|
struct sigaction act = (struct sigaction) { .sa_handler = SIG_IGN };
|
|
int rc = sigaction(SIGPIPE, &act, NULL);
|
|
assert(rc == 0);
|
|
|
|
while ((ch = getopt_long(argc, argv, "c:fd:s:u:g:a:p:vh", long_options, &longindex)) >= 0) {
|
|
switch (ch) {
|
|
case 'c':
|
|
config = optarg;
|
|
break;
|
|
case 'f':
|
|
log_daemon = 0;
|
|
break;
|
|
case 'd':
|
|
log_level = strtol(optarg, NULL, 0);
|
|
break;
|
|
case 'u':
|
|
uid = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'g':
|
|
gid = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'a': {
|
|
char *server_address, *token;
|
|
int i = 0;
|
|
|
|
server_address = strdup(optarg);
|
|
if (server_address == NULL) {
|
|
perror("strdup failed");
|
|
exit(-1);
|
|
}
|
|
|
|
token = strtok(server_address, " ");
|
|
|
|
while ((i < ADDR_MAX) && token) {
|
|
log_debug(0, "Address to listen: %s\n", token);
|
|
server_addresses[i] = token;
|
|
|
|
i++;
|
|
token = strtok(NULL, " ");
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 'p':
|
|
server_port = (uint16_t)strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'v':
|
|
printf("%s version %s\n", program_name, ISCSI_VERSION_STRING);
|
|
exit(0);
|
|
break;
|
|
case 'h':
|
|
usage(0);
|
|
break;
|
|
default:
|
|
usage(1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((ctrl_fd = kernel_open()) < 0)
|
|
exit(-1);
|
|
|
|
init_max_params();
|
|
|
|
if ((nl_fd = nl_open()) < 0) {
|
|
perror("netlink open failed");
|
|
exit(-1);
|
|
};
|
|
|
|
err = kernel_attr_add(NULL, ISCSI_ISNS_SERVER_ATTR_NAME,
|
|
S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR, 0);
|
|
if (err != 0)
|
|
exit(err);
|
|
|
|
err = kernel_attr_add(NULL, ISCSI_ENABLED_ATTR_NAME,
|
|
S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR, 0);
|
|
if (err != 0)
|
|
exit(err);
|
|
err = kernel_attr_add(NULL, ISCSI_ISNS_ENTITY_ATTR_NAME,
|
|
S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR, 0);
|
|
if (err != 0)
|
|
exit(err);
|
|
|
|
if ((ipc_fd = iscsi_adm_request_listen()) < 0) {
|
|
perror("Opening AF_LOCAL socket failed");
|
|
exit(-1);
|
|
}
|
|
|
|
log_init();
|
|
if (log_daemon) {
|
|
char buf[64];
|
|
pid_t pid;
|
|
int fd;
|
|
|
|
fd = open("/var/run/iscsi-scstd.pid", O_WRONLY|O_CREAT, 0644);
|
|
if (fd < 0) {
|
|
log_error("unable to create pid file");
|
|
exit(1);
|
|
}
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
log_error("starting daemon failed");
|
|
exit(1);
|
|
} else if (pid) {
|
|
int res = -1;
|
|
|
|
close(init_report_pipe[1]);
|
|
if (read(init_report_pipe[0], &res, sizeof(res)) < sizeof(res))
|
|
exit(-1);
|
|
else
|
|
exit(res);
|
|
}
|
|
|
|
if (chdir("/") < 0) {
|
|
log_error("failed to set working dir to /: %m");
|
|
exit(1);
|
|
}
|
|
|
|
if (lockf(fd, F_TLOCK, 0) < 0) {
|
|
log_error("unable to lock pid file");
|
|
exit(1);
|
|
}
|
|
if (ftruncate(fd, 0) < 0) {
|
|
log_error("failed to ftruncate the PID file: %m");
|
|
exit(1);
|
|
}
|
|
|
|
sprintf(buf, "%d\n", getpid());
|
|
if (write(fd, buf, strlen(buf)) < strlen(buf)) {
|
|
log_error("failed to write PID to PID file: %m");
|
|
exit(1);
|
|
}
|
|
|
|
close(0);
|
|
open("/dev/null", O_RDWR);
|
|
dup2(0, 1);
|
|
dup2(0, 2);
|
|
setsid();
|
|
}
|
|
|
|
err = config_load(config);
|
|
if (err != 0)
|
|
exit(1);
|
|
|
|
if (gid && setgid(gid) < 0)
|
|
log_error("setgid failed: %s", strerror(errno));
|
|
|
|
if (uid && setuid(uid) < 0)
|
|
log_error("setuid failed: %s", strerror(errno));
|
|
|
|
event_loop();
|
|
|
|
return 0;
|
|
}
|