/*
* Copyright (c) 2009-2011, Broadcom Corporation
* Copyright (c) 2014, QLogic Corporation
*
* Written by: Benjamin Li (benli@broadcom.com)
*
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Adam Dunkels.
* 4. 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 ``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 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.
*
* iscsi_ipc.c - Generic NIC management/utility functions
*
*/
#define _GNU_SOURCE
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <sys/types.h>
#include <pwd.h>
#define PFX "iscsi_ipc "
/* TODO fix me */
#define IFNAMSIZ 15
#include "nic.h"
#include "nic_utils.h"
#include "nic_vlan.h"
#include "options.h"
#include "mgmt_ipc.h"
#include "iscsid_ipc.h"
#include "uip.h"
#include "uip_mgmt_ipc.h"
#include "sysdeps.h"
#include "logger.h"
#include "uip.h"
#include "ping.h"
/* private iscsid options stucture */
struct iscsid_options {
int fd;
pthread_t thread;
};
struct iface_rec_decode {
/* General */
int32_t iface_num;
uint32_t ip_type;
/* IPv4 */
struct in_addr ipv4_addr;
struct in_addr ipv4_subnet_mask;
struct in_addr ipv4_gateway;
/* IPv6 */
struct in6_addr ipv6_addr;
struct in6_addr ipv6_subnet_mask;
uint32_t prefix_len;
struct in6_addr ipv6_linklocal;
struct in6_addr ipv6_router;
uint8_t ipv6_autocfg;
uint8_t linklocal_autocfg;
uint8_t router_autocfg;
uint8_t vlan_state;
uint8_t vlan_priority;
uint16_t vlan_id;
#define MIN_MTU_SUPPORT 46
#define MAX_MTU_SUPPORT 9000
uint16_t mtu;
};
#define PEERUSER_MAX 64
/******************************************************************************
* Globals
*****************************************************************************/
static struct iscsid_options iscsid_opts = {
.fd = INVALID_FD,
.thread = INVALID_THREAD,
};
/******************************************************************************
* iscsid Functions
*****************************************************************************/
static void *enable_nic_thread(void *data)
{
nic_t *nic = (nic_t *) data;
prepare_nic_thread(nic);
LOG_INFO(PFX "%s: started NIC enable thread state: 0x%x",
nic->log_name, nic->state)
/* Enable the NIC */
nic_enable(nic);
nic->enable_thread = INVALID_THREAD;
pthread_exit(NULL);
}
static int decode_cidr(char *in_ipaddr_str, struct iface_rec_decode *ird)
{
int rc = 0, i;
char *tmp, *tok;
char ipaddr_str[NI_MAXHOST];
char str[INET6_ADDRSTRLEN];
unsigned long keepbits = 0;
struct in_addr ia;
struct in6_addr ia6;
strlcpy(ipaddr_str, in_ipaddr_str, NI_MAXHOST);
/* Find the CIDR if any */
tmp = strchr(ipaddr_str, '/');
if (tmp) {
/* CIDR found, now decode, tmpbuf = ip, tmp = netmask */
tmp = ipaddr_str;
tok = strsep(&tmp, "/");
LOG_INFO(PFX "in cidr: bitmask '%s' ip '%s'", tmp, tok);
keepbits = strtoull(tmp, NULL, 10);
}
/* Determine if the IP address passed from the iface file is
* an IPv4 or IPv6 address */
rc = inet_pton(AF_INET, ipaddr_str, &ird->ipv6_addr);
if (rc == 0) {
/* Test to determine if the addres is an IPv6 address */
rc = inet_pton(AF_INET6, ipaddr_str, &ird->ipv6_addr);
if (rc == 0) {
LOG_ERR(PFX "Could not parse IP address: '%s'",
ipaddr_str);
goto out;
}
ird->ip_type = AF_INET6;
if (keepbits > 128) {
LOG_ERR(PFX "CIDR netmask > 128 for IPv6: %d(%s)",
keepbits, tmp);
goto out;
}
if (!keepbits) {
/* Default prefix mask to 64 */
memcpy(&ird->ipv6_subnet_mask.s6_addr, all_zeroes_addr6,
sizeof(struct in6_addr));
ird->prefix_len = 64;
for (i = 0; i < 2; i++)
ird->ipv6_subnet_mask.s6_addr32[i] = 0xffffffff;
goto out;
}
ird->prefix_len = keepbits;
memcpy(&ia6.s6_addr, all_zeroes_addr6, sizeof(struct in6_addr));
for (i = 0; i < 4; i++) {
if (keepbits < 32) {
ia6.s6_addr32[i] = keepbits > 0 ?
0x00 - (1 << (32 - keepbits)) : 0;
ia6.s6_addr32[i] = htonl(ia6.s6_addr32[i]);
break;
} else
ia6.s6_addr32[i] = 0xFFFFFFFF;
keepbits -= 32;
}
ird->ipv6_subnet_mask = ia6;
if (inet_ntop(AF_INET6, &ia6, str, sizeof(str)))
LOG_INFO(PFX "Using netmask: %s", str);
} else {
ird->ip_type = AF_INET;
rc = inet_pton(AF_INET, ipaddr_str, &ird->ipv4_addr);
if (keepbits > 32) {
LOG_ERR(PFX "CIDR netmask > 32 for IPv4: %d(%s)",
keepbits, tmp);
goto out;
}
ia.s_addr = keepbits > 0 ? 0x00 - (1 << (32 - keepbits)) : 0;
ird->ipv4_subnet_mask.s_addr = htonl(ia.s_addr);
LOG_INFO(PFX "Using netmask: %s",
inet_ntoa(ird->ipv4_subnet_mask));
}
out:
return rc;
}
static int decode_iface(struct iface_rec_decode *ird, struct iface_rec *rec)
{
int rc = 0;
char ipaddr_str[NI_MAXHOST];
/* Decodes the rec contents */
memset(ird, 0, sizeof(struct iface_rec_decode));
/* Detect for CIDR notation and strip off the netmask if present */
rc = decode_cidr(rec->ipaddress, ird);
if (rc && !ird->ip_type) {
LOG_ERR(PFX "cidr decode err: rc=%d, ip_type=%d",
rc, ird->ip_type);
/* Can't decode address, just exit */
return rc;
}
rc = 0;
ird->iface_num = rec->iface_num;
ird->vlan_id = rec->vlan_id;
if (rec->iface_num != IFACE_NUM_INVALID) {
ird->mtu = rec->mtu;
if (rec->vlan_id && strcmp(rec->vlan_state, "disable")) {
ird->vlan_state = 1;
ird->vlan_priority = rec->vlan_priority;
ird->vlan_id = rec->vlan_id;
}
if (ird->ip_type == AF_INET6) {
if (!strcmp(rec->ipv6_autocfg, "dhcpv6"))
ird->ipv6_autocfg = IPV6_AUTOCFG_DHCPV6;
else if (!strcmp(rec->ipv6_autocfg, "nd"))
ird->ipv6_autocfg = IPV6_AUTOCFG_ND;
else
ird->ipv6_autocfg = IPV6_AUTOCFG_NOTSPEC;
if (!strcmp(rec->linklocal_autocfg, "auto"))
ird->linklocal_autocfg = IPV6_LL_AUTOCFG_ON;
else if (!strcmp(rec->linklocal_autocfg, "off"))
ird->linklocal_autocfg = IPV6_LL_AUTOCFG_OFF;
else /* default */
ird->linklocal_autocfg = IPV6_LL_AUTOCFG_ON;
if (!strcmp(rec->router_autocfg, "auto"))
ird->router_autocfg = IPV6_RTR_AUTOCFG_ON;
else if (!strcmp(rec->router_autocfg, "off"))
ird->router_autocfg = IPV6_RTR_AUTOCFG_OFF;
else /* default */
ird->router_autocfg = IPV6_RTR_AUTOCFG_ON;
/* Decode the addresses based on the control flags */
/* For DHCP, ignore the IPv6 addr in the iface */
if (ird->ipv6_autocfg == IPV6_AUTOCFG_DHCPV6)
memcpy(&ird->ipv6_addr, all_zeroes_addr6,
sizeof(struct in6_addr));
/* Subnet mask priority: CIDR, then rec */
if (!ird->ipv6_subnet_mask.s6_addr)
inet_pton(AF_INET6, rec->subnet_mask,
&ird->ipv6_subnet_mask);
/* For LL on, ignore the IPv6 addr in the iface */
if (ird->linklocal_autocfg == IPV6_LL_AUTOCFG_OFF) {
strlcpy(ipaddr_str, rec->ipv6_linklocal,
NI_MAXHOST);
inet_pton(AF_INET6, ipaddr_str,
&ird->ipv6_linklocal);
}
/* For RTR on, ignore the IPv6 addr in the iface */
if (ird->router_autocfg == IPV6_RTR_AUTOCFG_OFF) {
strlcpy(ipaddr_str, rec->ipv6_router,
NI_MAXHOST);
inet_pton(AF_INET6, ipaddr_str,
&ird->ipv6_router);
}
} else {
/* Subnet mask priority: CIDR, rec, default */
if (!ird->ipv4_subnet_mask.s_addr)
inet_pton(AF_INET, rec->subnet_mask,
&ird->ipv4_subnet_mask);
if (!ird->ipv4_subnet_mask.s_addr)
ird->ipv4_subnet_mask.s_addr =
calculate_default_netmask(
ird->ipv4_addr.s_addr);
strlcpy(ipaddr_str, rec->gateway, NI_MAXHOST);
inet_pton(AF_INET, ipaddr_str, &ird->ipv4_gateway);
}
} else {
ird->ipv6_autocfg = IPV6_AUTOCFG_NOTUSED;
ird->linklocal_autocfg = IPV6_LL_AUTOCFG_NOTUSED;
ird->router_autocfg = IPV6_RTR_AUTOCFG_NOTUSED;
}
return rc;
}
static void *perform_ping(void *arg)
{
struct ping_conf *png_c = (struct ping_conf *)arg;
nic_interface_t *nic_iface = png_c->nic_iface;
nic_t *nic = nic_iface->parent;
iscsid_uip_broadcast_t *data;
struct sockaddr_in *addr;
struct sockaddr_in6 *addr6;
uip_ip6addr_t dst_addr;
int rc = 0;
int datalen;
struct timespec ts = {.tv_sec = 5,
.tv_nsec = 0};
data = (iscsid_uip_broadcast_t *)png_c->data;
datalen = data->u.ping_rec.datalen;
if ((datalen > STD_MTU_SIZE) || (datalen < 0)) {
LOG_ERR(PFX "Ping datalen invalid: %d", datalen);
rc = -EINVAL;
goto ping_done;
}
memset(dst_addr, 0, sizeof(uip_ip6addr_t));
if (nic_iface->protocol == AF_INET) {
/* IPv4 */
addr = (struct sockaddr_in *)&data->u.ping_rec.ipaddr;
memcpy(dst_addr, &addr->sin_addr.s_addr, sizeof(uip_ip4addr_t));
} else {
/* IPv6 */
addr6 = (struct sockaddr_in6 *)&data->u.ping_rec.ipaddr;
memcpy(dst_addr, &addr6->sin6_addr.s6_addr,
sizeof(uip_ip6addr_t));
}
/* Ensure that the NIC is RUNNING */
if ((nic->state != NIC_RUNNING) || !(nic->flags & NIC_ENABLED)) {
pthread_mutex_lock(&nic->nic_mutex);
rc = pthread_cond_timedwait(&nic->enable_done_cond,
&nic->nic_mutex, &ts);
if ((rc == 0) && (nic->state == NIC_RUNNING)) {
LOG_DEBUG(PFX "%s: nic running", nic->log_name);
} else if (rc) {
LOG_DEBUG(PFX "%s: err %d", nic->log_name, rc);
rc = -EAGAIN;
}
pthread_mutex_unlock(&nic->nic_mutex);
}
if (rc || nic->state != NIC_RUNNING) {
png_c->state = rc;
goto ping_done;
}
ping_init(png_c, dst_addr, nic_iface->protocol, datalen);
rc = do_ping_from_nic_iface(png_c);
if (png_c->state == -1)
png_c->state = rc;
ping_done:
LOG_INFO(PFX "ping thread end");
nic->ping_thread = INVALID_THREAD;
pthread_exit(NULL);
}
static int parse_iface(void *arg, int do_ping)
{
int rc, i;
nic_t *nic = NULL;
nic_interface_t *nic_iface;
char *transport_name;
size_t transport_name_size;
nic_lib_handle_t *handle;
iscsid_uip_broadcast_t *data;
char ipv6_buf_str[INET6_ADDRSTRLEN];
int request_type = 0;
struct iface_rec *rec;
struct iface_rec_decode ird;
struct in_addr src_match, dst_match;
pthread_attr_t attr;
struct ping_conf *png_c;
data = (iscsid_uip_broadcast_t *) arg;
if (do_ping)
rec = &data->u.ping_rec.ifrec;
else
rec = &data->u.iface_rec.rec;
LOG_INFO(PFX "Received request for '%s' to set IP address: '%s' "
"VLAN: '%d'",
rec->netdev,
rec->ipaddress,
rec->vlan_id);
rc = decode_iface(&ird, rec);
if (ird.vlan_id && valid_vlan(ird.vlan_id) == 0) {
LOG_ERR(PFX "Invalid VLAN tag: %d", ird.vlan_id);
rc = -EIO;
goto early_exit;
}
if (rc && !ird.ip_type) {
LOG_ERR(PFX "iface err: rc=%d, ip_type=%d", rc, ird.ip_type);
goto early_exit;
}
for (i = 0; i < 10; i++) {
struct timespec sleep_req, sleep_rem;
if (pthread_mutex_trylock(&nic_list_mutex) == 0)
break;
sleep_req.tv_sec = 0;
sleep_req.tv_nsec = 100000;
nanosleep(&sleep_req, &sleep_rem);
}
if (i >= 10) {
LOG_WARN(PFX "Could not acquire nic_list_mutex lock");
rc = -EIO;
goto early_exit;
}
/* nic_list_mutex locked */
/* Check if we can find the NIC device using the netdev
* name */
rc = from_netdev_name_find_nic(rec->netdev, &nic);
if (rc != 0) {
LOG_WARN(PFX "Couldn't find NIC: %s, creating an instance",
rec->netdev);
nic = nic_init();
if (nic == NULL) {
LOG_ERR(PFX "Couldn't allocate space for NIC %s",
rec->netdev);
rc = -ENOMEM;
goto done;
}
strncpy(nic->eth_device_name,
rec->netdev,
sizeof(nic->eth_device_name));
nic->config_device_name = nic->eth_device_name;
nic->log_name = nic->eth_device_name;
if (nic_fill_name(nic) != 0) {
free(nic);
rc = -EIO;
goto done;
}
nic_add(nic);
} else {
LOG_INFO(PFX " %s, using existing NIC",
rec->netdev);
}
pthread_mutex_lock(&nic->nic_mutex);
if (nic->flags & NIC_GOING_DOWN) {
pthread_mutex_unlock(&nic->nic_mutex);
rc = -EIO;
LOG_INFO(PFX "nic->flags GOING DOWN");
goto done;
}
/* If we retry too many times allow iscsid to timeout */
if (nic->pending_count > 1000) {
nic->pending_count = 0;
nic->flags &= ~NIC_ENABLED_PENDING;
pthread_mutex_unlock(&nic->nic_mutex);
LOG_WARN(PFX "%s: pending count exceeded 1000", nic->log_name);
rc = 0;
goto done;
}
if (nic->flags & NIC_ENABLED_PENDING) {
struct timespec sleep_req, sleep_rem;
nic->pending_count++;
pthread_mutex_unlock(&nic->nic_mutex);
sleep_req.tv_sec = 2;
sleep_req.tv_nsec = 0;
nanosleep(&sleep_req, &sleep_rem);
pthread_mutex_lock(&nic->nic_mutex);
if (!(nic->flags & NIC_ENABLED) ||
nic->state != NIC_RUNNING) {
pthread_mutex_unlock(&nic->nic_mutex);
LOG_INFO(PFX "%s: enabled pending", nic->log_name);
rc = -EAGAIN;
goto done;
}
}
pthread_mutex_unlock(&nic->nic_mutex);
prepare_library(nic);
/* Sanity Check to ensure the transport names are the same */
handle = nic->nic_library;
if (handle != NULL) {
(*handle->ops->lib_ops.get_transport_name) (&transport_name,
&transport_name_size);
if (strncmp(transport_name,
rec->transport_name,
transport_name_size) != 0) {
LOG_ERR(PFX "%s Transport name is not equal "
"expected: %s got: %s",
nic->log_name,
rec->transport_name,
transport_name);
}
} else {
LOG_ERR(PFX "%s Couldn't find nic library ", nic->log_name);
rc = -EIO;
goto done;
}
LOG_INFO(PFX "%s library set using transport_name %s",
nic->log_name, transport_name);
/* Determine how to configure the IP address */
if (ird.ip_type == AF_INET) {
if (memcmp(&ird.ipv4_addr,
all_zeroes_addr4, sizeof(uip_ip4addr_t)) == 0) {
LOG_INFO(PFX "%s: requesting configuration using DHCP",
nic->log_name);
request_type = IPV4_CONFIG_DHCP;
} else {
LOG_INFO(PFX "%s: requesting configuration using "
"static IP address", nic->log_name);
request_type = IPV4_CONFIG_STATIC;
}
} else if (ird.ip_type == AF_INET6) {
/* For the new 872_22, check ipv6_autocfg for DHCPv6 instead */
switch (ird.ipv6_autocfg) {
case IPV6_AUTOCFG_DHCPV6:
request_type = IPV6_CONFIG_DHCP;
break;
case IPV6_AUTOCFG_ND:
request_type = IPV6_CONFIG_STATIC;
break;
case IPV6_AUTOCFG_NOTSPEC:
/* Treat NOTSPEC the same as NOTUSED for now */
case IPV6_AUTOCFG_NOTUSED:
/* For 871 */
default:
/* Just the IP address to determine */
if (memcmp(&ird.ipv6_addr,
all_zeroes_addr6,
sizeof(struct in6_addr)) == 0)
request_type = IPV6_CONFIG_DHCP;
else
request_type = IPV6_CONFIG_STATIC;
}
} else {
LOG_ERR(PFX "%s: unknown ip_type to configure: 0x%x",
nic->log_name, ird.ip_type);
rc = -EIO;
goto done;
}
pthread_mutex_lock(&nic->nic_mutex);
nic_iface = nic_find_nic_iface(nic, ird.ip_type, ird.vlan_id,
ird.iface_num, request_type);
if (nic->flags & NIC_PATHREQ_WAIT) {
if (!nic_iface ||
!(nic_iface->flags & NIC_IFACE_PATHREQ_WAIT)) {
int pathreq_wait;
if (nic_iface &&
(nic_iface->flags & NIC_IFACE_PATHREQ_WAIT2))
pathreq_wait = 12;
else
pathreq_wait = 10;
if (nic->pathreq_pending_count < pathreq_wait) {
struct timespec sleep_req, sleep_rem;
pthread_mutex_unlock(&nic->nic_mutex);
nic->pathreq_pending_count++;
sleep_req.tv_sec = 0;
sleep_req.tv_nsec = 100000;
nanosleep(&sleep_req, &sleep_rem);
/* Somebody else is waiting for PATH_REQ */
LOG_INFO(PFX "%s: path req pending cnt=%d",
nic->log_name,
nic->pathreq_pending_count);
rc = -EAGAIN;
goto done;
} else {
nic->pathreq_pending_count = 0;
LOG_DEBUG(PFX "%s: path req pending cnt "
"exceeded!", nic->log_name);
/* Allow to fall thru */
}
}
}
nic->flags |= NIC_PATHREQ_WAIT;
/* Create the network interface if it doesn't exist */
if (nic_iface == NULL) {
LOG_DEBUG(PFX "%s couldn't find interface with "
"ip_type: 0x%x creating it",
nic->log_name, ird.ip_type);
nic_iface = nic_iface_init();
if (nic_iface == NULL) {
pthread_mutex_unlock(&nic->nic_mutex);
LOG_ERR(PFX "%s Couldn't allocate "
"interface with ip_type: 0x%x",
nic->log_name, ird.ip_type);
goto done;
}
nic_iface->protocol = ird.ip_type;
nic_iface->vlan_id = ird.vlan_id;
nic_iface->vlan_priority = ird.vlan_priority;
if (ird.mtu >= MIN_MTU_SUPPORT && ird.mtu <= MAX_MTU_SUPPORT)
nic_iface->mtu = ird.mtu;
nic_iface->iface_num = ird.iface_num;
nic_iface->request_type = request_type;
nic_add_nic_iface(nic, nic_iface);
persist_all_nic_iface(nic);
LOG_INFO(PFX "%s: created network interface",
nic->log_name);
} else {
/* Move the nic_iface to the front */
set_nic_iface(nic, nic_iface);
LOG_INFO(PFX "%s: using existing network interface",
nic->log_name);
}
nic_iface->flags |= NIC_IFACE_PATHREQ_WAIT1;
if (nic->nl_process_thread == INVALID_THREAD) {
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
rc = pthread_create(&nic->nl_process_thread, &attr,
nl_process_handle_thread, nic);
if (rc != 0) {
LOG_ERR(PFX "%s: Could not create NIC NL "
"processing thread [%s]", nic->log_name,
strerror(rc));
nic->nl_process_thread = INVALID_THREAD;
/* Reset both WAIT flags */
nic_iface->flags &= ~NIC_IFACE_PATHREQ_WAIT;
nic->flags &= ~NIC_PATHREQ_WAIT;
}
}
pthread_mutex_unlock(&nic->nic_mutex);
if (nic_iface->ustack.ip_config == request_type) {
/* Same request_type, check for STATIC address change */
if (request_type == IPV4_CONFIG_STATIC) {
if (memcmp(nic_iface->ustack.hostaddr, &ird.ipv4_addr,
sizeof(struct in_addr)))
goto reacquire;
} else if (request_type == IPV6_CONFIG_STATIC) {
if (memcmp(nic_iface->ustack.hostaddr6, &ird.ipv6_addr,
sizeof(struct in6_addr)))
goto reacquire;
else
inet_ntop(AF_INET6, &ird.ipv6_addr,
ipv6_buf_str,
sizeof(ipv6_buf_str));
}
LOG_INFO(PFX "%s: IP configuration didn't change using 0x%x",
nic->log_name, nic_iface->ustack.ip_config);
/* No need to acquire the IP address */
inet_ntop(AF_INET6, &ird.ipv6_addr, ipv6_buf_str,
sizeof(ipv6_buf_str));
goto enable_nic;
}
reacquire:
/* Config needs to re-acquire for this nic_iface */
pthread_mutex_lock(&nic->nic_mutex);
nic_iface->flags |= NIC_IFACE_ACQUIRE;
pthread_mutex_unlock(&nic->nic_mutex);
/* Disable the nic loop from further processing, upon returned,
the nic_iface should be cleared */
nic_disable(nic, 0);
/* Check to see if this is using DHCP or if this is
* a static IPv4 address. This is done by checking
* if the IP address is equal to 0.0.0.0. If it is
* then the user has specified to use DHCP. If not
* then the user has spcicied to use a static IP address
* an the default netmask will be used */
switch (request_type) {
case IPV4_CONFIG_DHCP:
memset(nic_iface->ustack.hostaddr, 0, sizeof(struct in_addr));
LOG_INFO(PFX "%s: configuring using DHCP", nic->log_name);
nic_iface->ustack.ip_config = IPV4_CONFIG_DHCP;
break;
case IPV4_CONFIG_STATIC:
memcpy(nic_iface->ustack.hostaddr, &ird.ipv4_addr,
sizeof(struct in_addr));
LOG_INFO(PFX "%s: configuring using static IP "
"IPv4 address :%s ",
nic->log_name, inet_ntoa(ird.ipv4_addr));
if (ird.ipv4_subnet_mask.s_addr)
memcpy(nic_iface->ustack.netmask,
&ird.ipv4_subnet_mask, sizeof(struct in_addr));
LOG_INFO(PFX " netmask: %s", inet_ntoa(ird.ipv4_subnet_mask));
/* Default route */
if (ird.ipv4_gateway.s_addr) {
/* Check for validity */
src_match.s_addr = ird.ipv4_addr.s_addr &
ird.ipv4_subnet_mask.s_addr;
dst_match.s_addr = ird.ipv4_gateway.s_addr &
ird.ipv4_subnet_mask.s_addr;
if (src_match.s_addr == dst_match.s_addr)
memcpy(nic_iface->ustack.default_route_addr,
&ird.ipv4_gateway,
sizeof(struct in_addr));
}
nic_iface->ustack.ip_config = IPV4_CONFIG_STATIC;
break;
case IPV6_CONFIG_DHCP:
memset(nic_iface->ustack.hostaddr6, 0,
sizeof(struct in6_addr));
nic_iface->ustack.prefix_len = ird.prefix_len;
nic_iface->ustack.ipv6_autocfg = ird.ipv6_autocfg;
nic_iface->ustack.linklocal_autocfg = ird.linklocal_autocfg;
nic_iface->ustack.router_autocfg = ird.router_autocfg;
if (memcmp(&ird.ipv6_subnet_mask, all_zeroes_addr6,
sizeof(struct in6_addr)))
memcpy(nic_iface->ustack.netmask6,
&ird.ipv6_subnet_mask, sizeof(struct in6_addr));
if (ird.linklocal_autocfg == IPV6_LL_AUTOCFG_OFF)
memcpy(nic_iface->ustack.linklocal6,
&ird.ipv6_linklocal, sizeof(struct in6_addr));
if (ird.router_autocfg == IPV6_RTR_AUTOCFG_OFF)
memcpy(nic_iface->ustack.default_route_addr6,
&ird.ipv6_router, sizeof(struct in6_addr));
inet_ntop(AF_INET6, &ird.ipv6_addr, ipv6_buf_str,
sizeof(ipv6_buf_str));
LOG_INFO(PFX "%s: configuring using DHCPv6",
nic->log_name);
nic_iface->ustack.ip_config = IPV6_CONFIG_DHCP;
break;
case IPV6_CONFIG_STATIC:
memcpy(nic_iface->ustack.hostaddr6, &ird.ipv6_addr,
sizeof(struct in6_addr));
nic_iface->ustack.prefix_len = ird.prefix_len;
nic_iface->ustack.ipv6_autocfg = ird.ipv6_autocfg;
nic_iface->ustack.linklocal_autocfg = ird.linklocal_autocfg;
nic_iface->ustack.router_autocfg = ird.router_autocfg;
if (memcmp(&ird.ipv6_subnet_mask, all_zeroes_addr6,
sizeof(struct in6_addr)))
memcpy(nic_iface->ustack.netmask6,
&ird.ipv6_subnet_mask, sizeof(struct in6_addr));
if (ird.linklocal_autocfg == IPV6_LL_AUTOCFG_OFF)
memcpy(nic_iface->ustack.linklocal6,
&ird.ipv6_linklocal, sizeof(struct in6_addr));
if (ird.router_autocfg == IPV6_RTR_AUTOCFG_OFF)
memcpy(nic_iface->ustack.default_route_addr6,
&ird.ipv6_router, sizeof(struct in6_addr));
inet_ntop(AF_INET6, &ird.ipv6_addr, ipv6_buf_str,
sizeof(ipv6_buf_str));
LOG_INFO(PFX "%s: configuring using static IP "
"IPv6 address: '%s'", nic->log_name, ipv6_buf_str);
nic_iface->ustack.ip_config = IPV6_CONFIG_STATIC;
break;
default:
LOG_INFO(PFX "%s: Unknown request type: 0x%x",
nic->log_name, request_type);
}
enable_nic:
switch (nic->state) {
case NIC_STOPPED:
/* This thread will be thrown away when completed */
if (nic->enable_thread != INVALID_THREAD) {
rc = pthread_cancel(nic->enable_thread);
if (rc != 0) {
LOG_INFO(PFX "%s: failed to cancel enable NIC "
"thread\n", nic->log_name);
goto eagain;
}
}
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
rc = pthread_create(&nic->enable_thread, &attr,
enable_nic_thread, (void *)nic);
if (rc != 0)
LOG_WARN(PFX "%s: failed starting enable NIC thread\n",
nic->log_name);
eagain:
rc = -EAGAIN;
break;
case NIC_RUNNING:
LOG_INFO(PFX "%s: NIC already enabled "
"flags: 0x%x state: 0x%x\n",
nic->log_name, nic->flags, nic->state);
rc = 0;
break;
default:
LOG_INFO(PFX "%s: NIC enable still in progress "
"flags: 0x%x state: 0x%x\n",
nic->log_name, nic->flags, nic->state);
rc = -EAGAIN;
}
LOG_INFO(PFX "ISCSID_UIP_IPC_GET_IFACE: command: %x "
"name: %s, netdev: %s ipaddr: %s vlan: %d transport_name:%s",
data->header.command, rec->name, rec->netdev,
(ird.ip_type == AF_INET) ? inet_ntoa(ird.ipv4_addr) :
ipv6_buf_str,
ird.vlan_id, rec->transport_name);
if (do_ping) {
if (nic->ping_thread != INVALID_THREAD) {
rc = pthread_cancel(nic->ping_thread);
if (rc != 0) {
LOG_INFO(PFX "%s: failed to cancel ping thread",
nic->log_name);
rc = -EAGAIN;
goto done;
}
}
png_c = malloc(sizeof(struct ping_conf));
if (!png_c) {
LOG_ERR(PFX "Memory alloc failed for ping conf");
rc = -ENOMEM;
goto done;
}
memset(png_c, 0, sizeof(struct ping_conf));
png_c->nic_iface = nic_iface;
png_c->data = arg;
nic_iface->ustack.ping_conf = png_c;
/* Spawn a thread to perform ping operation.
* This thread will exit when done.
*/
rc = pthread_create(&nic->ping_thread, NULL,
perform_ping, (void *)png_c);
if (rc != 0) {
LOG_WARN(PFX "%s: failed starting ping thread\n",
nic->log_name);
} else {
pthread_join(nic->ping_thread, NULL);
rc = png_c->state;
if (rc == -EAGAIN)
png_c->state = 0;
}
free(png_c);
nic_iface->ustack.ping_conf = NULL;
}
done:
pthread_mutex_unlock(&nic_list_mutex);
early_exit:
return rc;
}
/**
* process_iscsid_broadcast() - This function is used to process the
* broadcast messages from iscsid
*
* s2 is an open file descriptor, which
* must not be left open upon return
*/
int process_iscsid_broadcast(int s2)
{
int rc = 0;
iscsid_uip_broadcast_t *data;
iscsid_uip_rsp_t rsp;
FILE *fd;
size_t size;
iscsid_uip_cmd_e cmd;
uint32_t payload_len;
fd = fdopen(s2, "r+");
if (fd == NULL) {
LOG_ERR(PFX "Couldn't open file descriptor: %d(%s)",
errno, strerror(errno));
close(s2);
return -EIO;
}
/* This will be freed by parse_iface_thread() */
data = (iscsid_uip_broadcast_t *) calloc(1, sizeof(*data));
if (data == NULL) {
LOG_ERR(PFX "Couldn't allocate memory for iface data");
rc = -ENOMEM;
goto error;
}
memset(data, 0, sizeof(*data));
size = fread(data, sizeof(iscsid_uip_broadcast_header_t), 1, fd);
if (!size) {
LOG_ERR(PFX "Could not read request: %d(%s)",
errno, strerror(errno));
rc = ferror(fd);
goto error;
}
cmd = data->header.command;
payload_len = data->header.payload_len;
if (payload_len > sizeof(data->u)) {
LOG_ERR(PFX "Data payload length too large (%d). Corrupt payload?",
payload_len);
rc = -EINVAL;
goto error;
}
LOG_DEBUG(PFX "recv iscsid request: cmd: %d, payload_len: %d",
cmd, payload_len);
memset(&rsp, 0, sizeof(rsp));
switch (cmd) {
case ISCSID_UIP_IPC_GET_IFACE:
size = fread(&data->u.iface_rec, payload_len, 1, fd);
if (!size) {
LOG_ERR(PFX "Could not read data: %d(%s)",
errno, strerror(errno));
goto error;
}
rc = parse_iface(data, 0);
switch (rc) {
case 0:
rsp.command = cmd;
rsp.err = ISCSID_UIP_MGMT_IPC_DEVICE_UP;
break;
case -EAGAIN:
rsp.command = cmd;
rsp.err = ISCSID_UIP_MGMT_IPC_DEVICE_INITIALIZING;
break;
default:
rsp.command = cmd;
rsp.err = ISCSID_UIP_MGMT_IPC_ERR;
}
break;
case ISCSID_UIP_IPC_PING:
size = fread(&data->u.ping_rec, payload_len, 1, fd);
if (!size) {
LOG_ERR(PFX "Could not read data: %d(%s)",
errno, strerror(errno));
goto error;
}
rc = parse_iface(data, 1);
rsp.command = cmd;
rsp.ping_sc = rc;
switch (rc) {
case 0:
rsp.err = ISCSID_UIP_MGMT_IPC_DEVICE_UP;
break;
case -EAGAIN:
rsp.err = ISCSID_UIP_MGMT_IPC_DEVICE_INITIALIZING;
break;
default:
rsp.err = ISCSID_UIP_MGMT_IPC_ERR;
}
break;
default:
LOG_WARN(PFX "Unknown iscsid broadcast command: %x",
data->header.command);
/* Send a response back to iscsid to tell it the
operation succeeded */
rsp.command = cmd;
rsp.err = ISCSID_UIP_MGMT_IPC_OK;
break;
}
size = fwrite(&rsp, sizeof(rsp), 1, fd);
if (size == -1) {
LOG_ERR(PFX "Could not send response: %d(%s)",
errno, strerror(errno));
rc = ferror(fd);
}
error:
if (data)
free(data);
fclose(fd);
return rc;
}
static void iscsid_loop_close(void *arg)
{
close(iscsid_opts.fd);
LOG_INFO(PFX "iSCSI daemon socket closed");
}
/*
* check that the peer user is privilidged
*
* return 1 if peer is ok else 0
*
* XXX: this function is copied from iscsid_ipc.c and should be
* moved into a common library
*/
static int
mgmt_peeruser(int sock, char *user)
{
struct ucred peercred;
socklen_t so_len = sizeof(peercred);
struct passwd *pass;
errno = 0;
if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &peercred,
&so_len) != 0 || so_len != sizeof(peercred)) {
/* We didn't get a valid credentials struct. */
LOG_ERR(PFX "peeruser_unux: error receiving credentials: %m");
return 0;
}
pass = getpwuid(peercred.uid);
if (pass == NULL) {
LOG_ERR(PFX "peeruser_unix: unknown local user with uid %d",
(int) peercred.uid);
return 0;
}
strlcpy(user, pass->pw_name, PEERUSER_MAX);
return 1;
}
/**
* iscsid_loop() - This is the function which will process the broadcast
* messages from iscsid
*
*/
static void *iscsid_loop(void *arg)
{
int rc;
sigset_t set;
char user[PEERUSER_MAX];
pthread_cleanup_push(iscsid_loop_close, arg);
sigfillset(&set);
rc = pthread_sigmask(SIG_BLOCK, &set, NULL);
if (rc != 0) {
LOG_ERR(PFX
"Couldn't set signal mask for the iscisd listening "
"thread");
}
LOG_DEBUG(PFX "Started iscsid listening thread");
while (1) {
struct sockaddr_un remote;
socklen_t sock_len;
int s2;
LOG_DEBUG(PFX "Waiting for iscsid command");
sock_len = sizeof(remote);
s2 = accept(iscsid_opts.fd,
(struct sockaddr *)&remote, &sock_len);
if (s2 == -1) {
if (errno == EAGAIN) {
LOG_DEBUG("Got EAGAIN from accept");
sleep(1);
continue;
} else if (errno == EINTR) {
LOG_DEBUG("Got EINTR from accept");
/* The program is terminating, time to exit */
break;
}
LOG_ERR(PFX "Could not accept: %d(%s)",
s2, strerror(errno));
continue;
}
if (!mgmt_peeruser(iscsid_opts.fd, user) || strncmp(user, "root", PEERUSER_MAX)) {
close(s2);
LOG_ERR(PFX "Access error: non-administrative connection rejected");
break;
}
/* this closes the file descriptor s2 */
process_iscsid_broadcast(s2);
}
pthread_cleanup_pop(0);
LOG_ERR(PFX "exit iscsid listening thread");
pthread_exit(NULL);
}
#define SD_SOCKET_FDS_START 3
static int ipc_systemd(void)
{
char *env;
env = getenv("LISTEN_PID");
if (!env || (strtoul(env, NULL, 10) != getpid()))
return -EINVAL;
env = getenv("LISTEN_FDS");
if (!env)
return -EINVAL;
if (strtoul(env, NULL, 10) != 1) {
LOG_ERR("Did not receive exactly one IPC socket from systemd");
return -EINVAL;
}
return SD_SOCKET_FDS_START;
}
/******************************************************************************
* Initialize/Cleanup routines
******************************************************************************/
/**
* iscsid_init() - This function will setup the thread used to listen for
* the iscsid broadcast messages
* @return 0 on success, <0 on failure
*/
int iscsid_init()
{
int rc, addr_len;
struct sockaddr_un addr;
iscsid_opts.fd = ipc_systemd();
if (iscsid_opts.fd >= 0)
return 0;
iscsid_opts.fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (iscsid_opts.fd < 0) {
LOG_ERR(PFX "Can not create IPC socket");
return iscsid_opts.fd;
}
addr_len = offsetof(struct sockaddr_un, sun_path) + strlen(ISCSID_UIP_NAMESPACE) + 1;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_LOCAL;
memcpy((char *)&addr.sun_path + 1, ISCSID_UIP_NAMESPACE,
strlen(ISCSID_UIP_NAMESPACE));
rc = bind(iscsid_opts.fd, (struct sockaddr *)&addr, addr_len);
if (rc < 0) {
LOG_ERR(PFX "Can not bind IPC socket: %s", strerror(errno));
goto error;
}
rc = listen(iscsid_opts.fd, 32);
if (rc < 0) {
LOG_ERR(PFX "Can not listen IPC socket: %s", strerror(errno));
goto error;
}
return 0;
error:
close(iscsid_opts.fd);
iscsid_opts.fd = INVALID_FD;
return rc;
}
/**
* iscsid_start() - This function will start the thread used to listen for
* the iscsid broadcast messages
* @return 0 on success, <0 on failure
*/
int iscsid_start()
{
pthread_attr_t attr;
int rc;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
rc = pthread_create(&iscsid_opts.thread, &attr, iscsid_loop, NULL);
if (rc != 0) {
LOG_ERR(PFX "Could not start iscsid listening thread rc=%d",
rc);
goto error;
}
return 0;
error:
close(iscsid_opts.fd);
iscsid_opts.fd = INVALID_FD;
return rc;
}
/**
* iscsid_cleanup() - This is called when stoping the thread listening
* for the iscsid broadcast messages
*/
void iscsid_cleanup()
{
int rc;
if (iscsid_opts.fd != INVALID_FD &&
iscsid_opts.thread != INVALID_THREAD) {
rc = pthread_cancel(iscsid_opts.thread);
if (rc != 0) {
LOG_ERR("Could not cancel iscsid listening thread: %s",
strerror(rc));
}
}
LOG_INFO(PFX "iscsid listening thread has shutdown");
}