/*
* Copyright(c) 2010-2011 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
* Maintained at www.Open-FCoE.org
*/
#define __STDC_FORMAT_MACROS 1
#include <ctype.h>
#include <getopt.h>
#include <inttypes.h>
#include <malloc.h>
#include <signal.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <libgen.h>
#include <ulimit.h>
#include <unistd.h>
#include <paths.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/queue.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#include <linux/if.h>
#include <linux/if_arp.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/ethtool.h>
#include <linux/if_vlan.h>
#include <linux/dcbnl.h>
#include <lldpad/dcb_types.h>
#include <lldpad/clif.h>
#include <lldpad/lldp_dcbx_cmds.h>
#include "scsi_netlink_fc.h"
#include "fcoe_utils_version.h"
#include "fcoemon_utils.h"
#include "fcoemon.h"
#include "fcoe_clif.h"
#include "fcoe_utils.h"
#include "sysfs_hba.h"
#include "strarr.h"
#include "fip.h"
#include "rtnetlink.h"
#ifndef SYSCONFDIR
#define SYSCONFDIR "/etc"
#endif
#define CONFIG_DIR SYSCONFDIR "/fcoe"
#define CONFIG_MIN_VAL_LEN (1 + 2)
#define CONFIG_MAX_VAL_LEN (20 + 2)
#define DCB_APP_0_DEFAULT_ENABLE 1
#define DCB_APP_0_DEFAULT_WILLING 1
#define FILE_NAME_LEN (NAME_MAX + 1)
#define CFG_FILE_PREFIX "cfg-"
#define DEF_CFG_FILE CFG_FILE_PREFIX "ethx"
#define FCOE_VLAN_SUFFIX "-fcoe"
#define FCOE_VLAN_FORMAT "%s.%d" FCOE_VLAN_SUFFIX
#define FCOE_VID_SCAN_FORMAT "%*[^.].%d" FCOE_VLAN_SUFFIX
#define VLAN_DIR "/proc/net/vlan"
#define DCBD_CONNECT_TIMEOUT (10 * 1000 * 1000) /* 10 seconds */
#define DCBD_CONNECT_RETRY_TIMEOUT (1 * 1000 * 1000) /* 1 seconds */
#define DCBD_REQ_RETRY_TIMEOUT (200 * 1000) /* 0.2 seconds */
#define DCBD_MAX_REQ_RETRIES 10
#define FCM_PING_REQ_LEN 1 /* byte-length of dcbd PING request */
#define FCM_PING_RSP_LEN 8 /* byte-length of dcbd PING response */
#define FCM_VLAN_DISC_TIMEOUT (1000 * 1000) /* 1 seconds */
#define DEF_RX_BUF_SIZE 4096
#define NLA_DATA(nla) ((void *)((char *)(nla) + NLA_HDRLEN))
#define NLA_NEXT(nla) (struct rtattr *)((char *)nla + NLMSG_ALIGN(nla->rta_len))
#define FCOE_ETH_TYPE 0x8906
#define CFG_IF_VAR_FCOEENABLE "FCOE_ENABLE"
#define CFG_IF_VAR_DCBREQUIRED "DCB_REQUIRED"
#define CFG_IF_VAR_AUTOVLAN "AUTO_VLAN"
#define CFG_IF_VAR_MODE "MODE"
#define CFG_IF_VAR_FIP_RESP "FIP_RESP"
enum fcoe_mode {
FCOE_MODE_FABRIC = 0,
FCOE_MODE_VN2VN = 1,
};
static bool force_legacy;
static sigset_t block_sigset;
void fcm_vlan_disc_timeout(void *arg);
/*
* fcoe service configuration data
* Note: These information are read in from the fcoe service
* files in CONFIG_DIR
*/
struct fcoe_port {
struct fcoe_port *next;
/* information from fcoe configuration files in CONFIG_DIR */
char ifname[IFNAMSIZ]; /* netif on which fcoe i/f is created */
char real_ifname[IFNAMSIZ]; /* underlying net ifname - e.g. if ifname
is a VLAN */
int fcoe_enable;
int dcb_required;
enum fcoe_mode mode;
bool fip_resp;
int auto_vlan;
int auto_created;
int ready;
/* following track data required to manage FCoE interface state */
enum fcp_action action; /* current state */
enum fcp_action last_action; /* last action */
int last_msg_type; /* last rtnetlink msg type received on if name */
struct sock_info *sock_reply;
int ifindex;
unsigned char mac[ETHER_ADDR_LEN];
struct sa_timer vlan_disc_timer;
int vlan_disc_count;
int fip_socket;
int fip_responder_socket;
char fchost[FCHOSTBUFLEN];
char ctlr[FCHOSTBUFLEN];
uint32_t last_fc_event_num;
};
enum fcoeport_ifname {
FCP_CFG_IFNAME = 0,
FCP_REAL_IFNAME
};
/*
* Interact with DCB daemon.
*/
static void fcm_dcbd_timeout(void *);
static void fcm_dcbd_retry_timeout(void *);
static void fcm_dcbd_disconnect(void);
static int fcm_dcbd_request(char *);
static void fcm_dcbd_rx(void *);
static void fcm_dcbd_event(char *, size_t);
static void fcm_dcbd_cmd_resp(char *, cmd_status);
static void fcm_netif_advance(struct fcm_netif *);
static void fcm_fcoe_action(struct fcoe_port *);
static void fcp_set_next_action(struct fcoe_port *, enum fcp_action);
static enum fcoe_status fcm_fcoe_if_action(char *, char *);
/*
* Used for backwards compatibility amongst libfcoe
* "control" interfaces.
*/
struct libfcoe_interface_template {
enum fcoe_status (*create)(struct fcoe_port *);
enum fcoe_status (*destroy)(struct fcoe_port *);
enum fcoe_status (*enable)(struct fcoe_port *);
enum fcoe_status (*disable)(struct fcoe_port *);
};
static const struct libfcoe_interface_template *libfcoe_control;
static enum fcoe_status fcm_module_create(struct fcoe_port *p)
{
enum fcoe_status rc;
switch (p->mode) {
case FCOE_MODE_VN2VN:
rc = fcm_fcoe_if_action(FCOE_CREATE_VN2VN, p->ifname);
break;
case FCOE_MODE_FABRIC:
default:
rc = fcm_fcoe_if_action(FCOE_CREATE, p->ifname);
break;
}
if (rc)
return rc;
/*
* This call validates that the interface name
* has an active fcoe session by checking for
* the fc_host in sysfs.
*/
if (fcoe_find_fchost(p->ifname, p->fchost, FCHOSTBUFLEN)) {
FCM_LOG_DBG("Failed to find fc_host for %s\n", p->ifname);
return ENOSYSFS;
}
return SUCCESS;
}
static enum fcoe_status fcm_module_destroy(struct fcoe_port *p)
{
return fcm_fcoe_if_action(FCOE_DESTROY, p->ifname);
}
static enum fcoe_status fcm_module_enable(struct fcoe_port *p)
{
return fcm_fcoe_if_action(FCOE_ENABLE, p->ifname);
}
static enum fcoe_status fcm_module_disable(struct fcoe_port *p)
{
return fcm_fcoe_if_action(FCOE_DISABLE, p->ifname);
}
static struct libfcoe_interface_template libfcoe_module_tmpl = {
.create = fcm_module_create,
.destroy = fcm_module_destroy,
.enable = fcm_module_enable,
.disable = fcm_module_disable,
};
static enum fcoe_status fcm_bus_enable(struct fcoe_port *p)
{
return fcm_write_str_to_ctlr_attr(p->ctlr, FCOE_CTLR_ATTR_ENABLED, "1");
}
static int fcm_bus_configure(struct fcoe_port *p)
{
int rc;
if (p->mode != FCOE_MODE_VN2VN)
return 0;
rc = fcm_write_str_to_ctlr_attr(p->ctlr, FCOE_CTLR_ATTR_MODE, "vn2vn");
return rc;
}
static enum fcoe_status fcm_bus_create(struct fcoe_port *p)
{
enum fcoe_status rc;
rc = fcm_write_str_to_sysfs_file(FCOE_BUS_CREATE, p->ifname);
if (rc)
return rc;
/*
* This call validates that the interface name
* has an active fcoe session by checking for
* the fc_host in sysfs.
*/
if (fcoe_find_fchost(p->ifname, p->fchost, FCHOSTBUFLEN)) {
FCM_LOG_DBG("Failed to find fc_host for %s\n", p->ifname);
return ENOSYSFS;
}
/*
* The fcoe_ctlr_device lookup only happens when the fcoe_sysfs
* kernel interfaces are used. It is a defect if p->ctlr is used
* outside of these abstracted routines.
*/
if (fcoe_find_ctlr(p->fchost, p->ctlr, FCHOSTBUFLEN)) {
FCM_LOG_DBG("Failed to get ctlr for %s\n", p->ifname);
return ENOSYSFS;
}
rc = fcm_bus_configure(p);
if (!rc)
rc = fcm_bus_enable(p);
return rc;
}
static enum fcoe_status fcm_bus_destroy(struct fcoe_port *p)
{
return fcm_write_str_to_sysfs_file(FCOE_BUS_DESTROY, p->ifname);
}
static enum fcoe_status fcm_bus_disable(struct fcoe_port *p)
{
return fcm_write_str_to_ctlr_attr(p->ctlr, FCOE_CTLR_ATTR_ENABLED, "0");
}
static struct libfcoe_interface_template libfcoe_bus_tmpl = {
.create = fcm_bus_create,
.destroy = fcm_bus_destroy,
.enable = fcm_bus_enable,
.disable = fcm_bus_disable,
};
struct fcm_clif {
int cl_fd;
int cl_busy; /* non-zero if command pending */
int cl_ping_pending;
struct sockaddr_un cl_local;
};
static struct fcm_clif fcm_clif_st;
static struct fcm_clif *fcm_clif = &fcm_clif_st;
static struct sa_timer fcm_dcbd_timer;
/* Debugging routine */
static void print_errors(int errors);
struct fcm_netif_head fcm_netif_head = TAILQ_HEAD_INITIALIZER(fcm_netif_head);
static int fcm_fc_socket;
static int fcm_link_socket;
static int fcm_link_seq;
static void fcm_link_recv(void *);
static void fcm_link_getlink(void);
static void clear_dcbd_info(struct fcm_netif *ff);
static int fcoe_vid_from_ifname(const char *ifname);
/*
* Table for getopt_long(3).
*/
static struct option fcm_options[] = {
{"debug", 1, NULL, 'd'},
{"legacy", 0, NULL, 'l'},
{"syslog", 1, NULL, 's'},
{"exec", 1, NULL, 'e'},
{"foreground", 0, NULL, 'f'},
{"version", 0, NULL, 'v'},
{NULL, 0, NULL, 0}
};
char progname[20];
/*
* Issue with buffer size: It isn't clear how to read more than one
* buffer's worth of GETLINK replies. The kernel seems to just drop the
* interface messages if they don't fit in the buffer, so we just make it
* large enough to fit and expand it if we ever do a read that almost fills it.
*/
static char *fcm_link_buf;
static size_t fcm_link_buf_size = 8192; /* initial size */
/*
* A value must be surrounded by quates, e.g. "x".
* The minimum length of a value is 1 excluding the quotes.
* The maximum length of a value is 20 excluding the quotes.
*/
static int fcm_remove_quotes(char *buf, int len)
{
char *s = buf;
char *e = buf + len - 1;
char tmp[CONFIG_MAX_VAL_LEN + 1];
if (len < CONFIG_MIN_VAL_LEN)
return -1;
if ((*s >= '0' && *s <= '9') ||
(*s >= 'a' && *s <= 'z') ||
(*s >= 'A' && *s <= 'Z'))
return -1;
if ((*e >= '0' && *e <= '9') ||
(*e >= 'a' && *e <= 'z') ||
(*e >= 'A' && *e <= 'Z'))
return -1;
s = buf + 1;
*e = '\0';
strncpy(tmp, s, len - 1);
strncpy(buf, tmp, len - 1);
return 0;
}
/*
* Read a configuration variable for a port from a config file.
* There's no problem if the file doesn't exist.
* The buffer is set to an empty string if the variable is not found.
*
* Returns: 1 found
* 0 not found
* -1 error in format
*/
static size_t fcm_read_config_variable(char *file, char *val_buf, size_t len,
FILE *fp, const char *var_name)
{
char *s;
char *var;
char *val;
char buf[FILE_NAME_LEN];
int n;
val_buf[0] = '\0';
buf[sizeof(buf) - 1] = '\0';
rewind(fp);
while ((s = fgets(buf, sizeof(buf) - 1, fp)) != NULL) {
while (isspace(*s))
s++;
if (*s == '\0' || *s == '#')
continue;
var = s;
if (!isalpha(*var))
continue;
val = strchr(s, '=');
if (val == NULL)
continue;
*val++ = '\0';
s = val;
if (strcmp(var_name, var) != 0)
continue;
while (*s != '\0' && !isspace(*s))
s++;
*s = '\0';
n = snprintf(val_buf, len, "%s", val);
if (fcm_remove_quotes(val_buf, n) < 0) {
FCM_LOG("Invalid format in config file"
" %s: %s=%s\n",
file, var_name, val);
/* error */
return -1;
}
/* found */
FCM_LOG_DBG("%s: %s = %s\n", file, var_name, val);
return 1;
}
/* not found */
return 0;
}
static struct fcoe_port *alloc_fcoe_port(char *ifname)
{
struct fcoe_port *p = NULL;
p = (struct fcoe_port *) calloc(1, sizeof(struct fcoe_port));
if (p) {
snprintf(p->ifname, sizeof(p->ifname), "%s", ifname);
p->action = FCP_WAIT;
/* last_action is initialized to FCP_DESTROY_IF to indicate
* that the interface is not created yet.
*/
p->last_action = FCP_DESTROY_IF;
p->fip_socket = -1;
p->fip_responder_socket = -1;
p->fchost[0] = '\0';
p->last_fc_event_num = 0;
sa_timer_init(&p->vlan_disc_timer, fcm_vlan_disc_timeout, p);
p->ready = 1;
}
return p;
}
static bool real_ifname_from_name(char *real_ifname, const char *ifname)
{
const char *sep;
sep = index(ifname, '.');
if (!sep)
return false;
memset(real_ifname, 0, IFNAMSIZ);
memcpy(real_ifname, ifname, sep - ifname);
return true;
}
static int fcm_read_config_files(void)
{
char file[80];
FILE *fp;
char val[CONFIG_MAX_VAL_LEN + 1];
DIR *dir;
struct dirent *dp;
struct fcoe_port *curr = NULL;
struct fcoe_port *next = NULL;
int rc;
sigprocmask(SIG_BLOCK, &block_sigset, NULL);
dir = opendir(CONFIG_DIR);
if (dir == NULL) {
FCM_LOG_ERR(errno, "Failed reading directory %s\n", CONFIG_DIR);
return -1;
}
for (;;) {
dp = readdir(dir);
if (dp == NULL)
break;
if (dp->d_name[0] == '.' &&
(dp->d_name[1] == '\0' ||
(dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
continue;
rc = strncmp(dp->d_name, CFG_FILE_PREFIX,
strlen(CFG_FILE_PREFIX));
if (rc)
continue;
if (!strncmp(dp->d_name, DEF_CFG_FILE,
strlen(DEF_CFG_FILE)))
continue;
next = alloc_fcoe_port(dp->d_name + 4);
if (!next) {
FCM_LOG_ERR(errno, "failed to allocate fcoe_port %s",
dp->d_name);
continue;
}
strncpy(file, CONFIG_DIR "/", sizeof(file));
strncat(file, dp->d_name, sizeof(file) - strlen(file));
file[sizeof(file) - 1] = '\0';
fp = fopen(file, "r");
if (!fp) {
FCM_LOG_ERR(errno, "Failed to read %s\n", file);
free(next);
continue;
}
real_ifname_from_name(next->real_ifname, next->ifname);
/* FCOE_ENABLE */
rc = fcm_read_config_variable(file, val, sizeof(val),
fp, CFG_IF_VAR_FCOEENABLE);
if (rc < 0) {
FCM_LOG("Invalid format for %s variable in %s",
CFG_IF_VAR_FCOEENABLE, file);
fclose(fp);
free(next);
continue;
}
/* if not found, default to "no" */
if (!strncasecmp(val, "yes", 3) && rc == 1)
next->fcoe_enable = 1;
/* DCB_REQUIRED */
rc = fcm_read_config_variable(file, val, sizeof(val),
fp, CFG_IF_VAR_DCBREQUIRED);
if (rc < 0) {
FCM_LOG("Invalid format for %s variable in %s",
CFG_IF_VAR_DCBREQUIRED, file);
fclose(fp);
free(next);
continue;
}
/* if not found, default to "no" */
if (!strncasecmp(val, "yes", 3) && rc == 1)
next->dcb_required = 1;
if (next->dcb_required == 1 && fcoe_config.dcb_init == 0)
fcoe_config.dcb_init = 1;
/* AUTO_VLAN */
rc = fcm_read_config_variable(file, val, sizeof(val),
fp, CFG_IF_VAR_AUTOVLAN);
if (rc < 0) {
FCM_LOG("Invalid format for %s variable in %s",
CFG_IF_VAR_AUTOVLAN, file);
fclose(fp);
free(next);
continue;
}
/* if not found, default to "no" */
if (!strncasecmp(val, "yes", 3) && rc == 1)
next->auto_vlan = 1;
/* FIP_RESP */
rc = fcm_read_config_variable(file, val, sizeof(val),
fp, CFG_IF_VAR_FIP_RESP);
if (rc < 0) {
FCM_LOG("Invalid format for %s variable in %s",
CFG_IF_VAR_FIP_RESP, file);
fclose(fp);
free(next);
continue;
}
/* if not found, default to "none" */
next->fip_resp = false;
if (!strcasecmp(val, "yes") && rc == 1) {
FCM_LOG("Starting FIP responder on %s", next->ifname);
next->fip_resp = true;
}
/* MODE */
rc = fcm_read_config_variable(file, val, sizeof(val),
fp, CFG_IF_VAR_MODE);
if (rc < 0) {
FCM_LOG("Invalid format for %s variable in %s",
CFG_IF_VAR_MODE, file);
fclose(fp);
free(next);
continue;
}
/* if not found, default to "fabric" */
next->mode = FCOE_MODE_FABRIC;
if (!strncasecmp(val, "vn2vn", 5) && rc == 1)
next->mode = FCOE_MODE_VN2VN;
fclose(fp);
if (!fcoe_config.port) {
fcoe_config.port = next;
curr = next;
} else {
curr->next = next;
curr = next;
}
}
closedir(dir);
sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);
return 0;
}
/*
* Given an fcoe_port pointer and an ifname, find the next fcoe_port
* in the list with a real ifname of 'ifname'.
*
* Returns: fcoe_port pointer to fcoe port entry
* NULL - if not found
*/
static struct fcoe_port *fcm_find_next_fcoe_port(struct fcoe_port *p,
char *ifname)
{
struct fcoe_port *np;
struct fcoe_port *found_port = NULL;
sigprocmask(SIG_BLOCK, &block_sigset, NULL);
np = fcoe_config.port;
while (np) {
if (np == p)
break;
np = np->next;
}
if (np)
np = np->next;
while (np) {
if (!strncmp(ifname, np->real_ifname, IFNAMSIZ)) {
found_port = np;
break;
}
np = np->next;
}
sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);
return found_port;
}
static struct fcoe_port *fcm_find_fcoe_port(char *ifname,
enum fcoeport_ifname t)
{
struct fcoe_port *p;
struct fcoe_port *found_port = NULL;
char *fp_ifname;
sigprocmask(SIG_BLOCK, &block_sigset, NULL);
p = fcoe_config.port;
while (p) {
switch (t) {
case FCP_CFG_IFNAME:
fp_ifname = p->ifname;
break;
case FCP_REAL_IFNAME:
fp_ifname = p->real_ifname;
break;
default:
FCM_LOG("unhandled interface type [%d] for %s",
t, ifname);
goto found;
}
if (!strncmp(ifname, fp_ifname, IFNAMSIZ)) {
found_port = p;
goto found;
}
p = p->next;
}
found:
sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);
return found_port;
}
static struct fcoe_port *fcm_find_port_by_host(uint16_t host_no)
{
struct fcoe_port *p;
struct fcoe_port *found_port = NULL;
char host[FCHOSTBUFLEN];
sigprocmask(SIG_BLOCK, &block_sigset, NULL);
snprintf(host, FCHOSTBUFLEN, "host%d", host_no);
p = fcoe_config.port;
while (p) {
if (!strncmp(p->fchost, host, FCHOSTBUFLEN)) {
found_port = p;
break;
}
p = p->next;
}
sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);
return found_port;
}
static void fcm_fc_event_handler(struct fc_nl_event *fc_event)
{
struct fcoe_port *p = fcm_find_port_by_host(fc_event->host_no);
if (!p)
return;
switch (fc_event->event_code) {
case HBA_EVENT_LIP_RESET_OCCURRED:
if (!p->last_fc_event_num &&
fc_event->event_num == p->last_fc_event_num)
return;
if (!p->auto_created && !p->auto_vlan)
return;
p->last_fc_event_num = fc_event->event_num;
/* find real interface port and re-activate again */
p = fcm_find_fcoe_port(p->real_ifname, FCP_CFG_IFNAME);
if (p && p->last_action != FCP_DISABLE_IF)
fcp_set_next_action(p, FCP_ACTIVATE_IF);
break;
default:
FCM_LOG("unsupported fc event:%d for host:%d\n",
fc_event->event_code, fc_event->host_no);
}
}
static int log_nlmsg_error(struct nlmsghdr *hp, size_t rlen, const char *str)
{
struct nlmsgerr *ep;
if (NLMSG_OK(hp, rlen)) {
ep = (struct nlmsgerr *)NLMSG_DATA(hp);
FCM_LOG_DBG("%s, err=%d, type=%d\n",
str, ep->error, ep->msg.nlmsg_type);
return ep->error;
} else {
FCM_LOG("%s", str);
return 0;
}
}
static void fcm_fc_event_log(struct fc_nl_event *fe)
{
/* from kernel "include/scsi/scsi_transport_fc.h" */
enum fc_host_event_code {
FCH_EVT_LIP = 0x1,
FCH_EVT_LINKUP = 0x2,
FCH_EVT_LINKDOWN = 0x3,
FCH_EVT_LIPRESET = 0x4,
FCH_EVT_RSCN = 0x5,
FCH_EVT_ADAPTER_CHANGE = 0x103,
FCH_EVT_PORT_UNKNOWN = 0x200,
FCH_EVT_PORT_OFFLINE = 0x201,
FCH_EVT_PORT_ONLINE = 0x202,
FCH_EVT_PORT_FABRIC = 0x204,
FCH_EVT_LINK_UNKNOWN = 0x500,
FCH_EVT_VENDOR_UNIQUE = 0xffff,
};
/* from kernel "drivers/scsi/scsi_transport_fc.c" */
const struct {
enum fc_host_event_code value;
char *name;
} fc_host_event_code_names[] = {
{ FCH_EVT_LIP, "lip" },
{ FCH_EVT_LINKUP, "link_up" },
{ FCH_EVT_LINKDOWN, "link_down" },
{ FCH_EVT_LIPRESET, "lip_reset" },
{ FCH_EVT_RSCN, "rscn" },
{ FCH_EVT_ADAPTER_CHANGE, "adapter_chg" },
{ FCH_EVT_PORT_UNKNOWN, "port_unknown" },
{ FCH_EVT_PORT_ONLINE, "port_online" },
{ FCH_EVT_PORT_OFFLINE, "port_offline" },
{ FCH_EVT_PORT_FABRIC, "port_fabric" },
{ FCH_EVT_LINK_UNKNOWN, "link_unknown" },
{ FCH_EVT_VENDOR_UNIQUE, "vendor_unique" },
};
unsigned int i;
for (i = 0; i < ARRAY_SIZE(fc_host_event_code_names); i++) {
if (fe->event_code == fc_host_event_code_names[i].value) {
/* only do u32 data even len is not, e.g. vendor */
FCM_LOG_DBG("FC_HOST_EVENT %d at %" PRIu64 " secs on "
"host%d code %d=%s datalen %d data=%d\n",
fe->event_num, fe->seconds,
fe->host_no, fe->event_code,
fc_host_event_code_names[i].name,
fe->event_datalen, fe->event_data);
break;
}
}
}
static void fcm_fc_event_recv(UNUSED void *arg)
{
struct nlmsghdr *hp;
struct fc_nl_event *fc_event;
size_t plen;
size_t rlen;
char *buf;
int rc;
buf = malloc(DEF_RX_BUF_SIZE);
if (!buf) {
FCM_LOG_ERR(errno, "failed to allocate FC event buffer\n");
return;
}
rc = read(fcm_fc_socket, buf, DEF_RX_BUF_SIZE);
if (!rc)
goto free_buf;
if (rc < 0) {
FCM_LOG_ERR(errno, "fc read error");
goto free_buf;
}
hp = (struct nlmsghdr *)buf;
rlen = rc;
for (hp = (struct nlmsghdr *)buf; NLMSG_OK(hp, rlen);
hp = NLMSG_NEXT(hp, rlen)) {
if (hp->nlmsg_type == NLMSG_DONE)
break;
if (hp->nlmsg_type == NLMSG_ERROR) {
log_nlmsg_error(hp, rlen, "fc nlmsg error");
break;
}
plen = NLMSG_PAYLOAD(hp, 0);
fc_event = (struct fc_nl_event *)NLMSG_DATA(hp);
if (plen < sizeof(*fc_event)) {
FCM_LOG("too short (%zu) to be an FC event", rlen);
break;
}
fcm_fc_event_log(fc_event);
fcm_fc_event_handler(fc_event);
}
free_buf:
free(buf);
}
static int fcm_fc_events_init(void)
{
int fd, rc;
struct sockaddr_nl fc_local;
fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_SCSITRANSPORT);
if (fd < 0) {
FCM_LOG_ERR(errno, "fc socket error");
return fd;
}
memset(&fc_local, 0, sizeof(fc_local));
fc_local.nl_family = AF_NETLINK;
fc_local.nl_groups = ~0;
fc_local.nl_pid = getpid();
rc = bind(fd, (struct sockaddr *)&fc_local, sizeof(fc_local));
if (rc == -1) {
FCM_LOG_ERR(errno, "fc socket bind error");
close(fd);
return rc;
}
fcm_fc_socket = fd;
/* Add a given file descriptor readfds set with its rx handler */
sa_select_add_fd(fd, fcm_fc_event_recv, NULL, NULL, NULL);
return 0;
}
static int fcm_link_init(void)
{
int fd;
int rc;
struct sockaddr_nl l_local;
fcm_link_buf = malloc(fcm_link_buf_size);
ASSERT(fcm_link_buf);
fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (fd < 0) {
FCM_LOG_ERR(errno, "socket error");
return fd;
}
memset(&l_local, 0, sizeof(l_local));
l_local.nl_family = AF_NETLINK;
l_local.nl_groups = RTMGRP_LINK | (1 << (RTNLGRP_DCB - 1));
l_local.nl_pid = 0;
rc = bind(fd, (struct sockaddr *)&l_local, sizeof(l_local));
if (rc == -1) {
FCM_LOG_ERR(errno, "bind error");
close(fd);
return rc;
}
fcm_link_socket = fd;
/* Add a given file descriptor from a readfds set */
sa_select_add_fd(fd, fcm_link_recv, NULL, NULL, NULL);
fcm_link_getlink();
return 0;
}
static struct fcoe_port *
fcm_port_create(char *ifname, enum clif_flags flags, int cmd);
static struct fcoe_port *fcm_new_vlan(int ifindex, int vid, bool vn2vn)
{
char real_name[IFNAMSIZ];
char vlan_name[IFNAMSIZ];
struct fcoe_port *p;
static const int flags[] = {
[false] = CLIF_FLAGS_FABRIC,
[true] = CLIF_FLAGS_VN2VN,
};
int rc;
if (vn2vn)
FCM_LOG_DBG("Auto VLAN found vn2vn on VID %d\n", vid);
else
FCM_LOG_DBG("Auto VLAN Found FCF on VID %d\n", vid);
if (rtnl_find_vlan(ifindex, vid, vlan_name)) {
rtnl_get_linkname(ifindex, real_name);
rc = snprintf(vlan_name, sizeof(vlan_name), FCOE_VLAN_FORMAT,
real_name, vid);
if (rc < 0 || (size_t) rc >= sizeof(vlan_name)) {
FCM_LOG("Warning: Generating FCoE VLAN device name for"
"interface %s VLAN %d: format resulted in a"
"name larger than IFNAMSIZ\n", real_name, vid);
vlan_name[sizeof(vlan_name) - 1] = 0;
FCM_LOG("\tTruncating VLAN name to %s\n", vlan_name);
}
vlan_create(ifindex, vid, vlan_name);
}
rtnl_set_iff_up(0, vlan_name);
p = fcm_find_fcoe_port(vlan_name, FCP_CFG_IFNAME);
if (p && !p->fcoe_enable)
return p;
p = fcm_port_create(vlan_name, flags[vn2vn], FCP_ACTIVATE_IF);
p->auto_created = 1;
return p;
}
static int
fcm_vlan_disc_handler(struct fiphdr *fh, struct sockaddr_ll *sa, void *arg)
{
int vid;
unsigned char mac[ETHER_ADDR_LEN];
int len = ntohs(fh->fip_desc_len);
struct fip_tlv_hdr *tlv = (struct fip_tlv_hdr *)(fh + 1);
struct fcoe_port *p = arg;
struct fcoe_port *vp;
int desc_mask = 0;
bool vn2vn = false;
enum {
VALID_MAC = 1,
VALID_VLAN = 2,
};
if (ntohs(fh->fip_proto) != FIP_PROTO_VLAN)
return -1;
if (fh->fip_subcode == FIP_VLAN_NOTE_VN2VN &&
(!p->auto_vlan || p->mode != FCOE_MODE_VN2VN)) {
FCM_LOG_DBG("%s: vn2vn vlan notif: auto_vlan=%d, mode=%d\n",
__func__, p->auto_vlan, p->mode);
return -1;
}
if (fh->fip_subcode != FIP_VLAN_NOTE &&
fh->fip_subcode != FIP_VLAN_NOTE_VN2VN) {
FCM_LOG_DBG("%s: fip_subcode=%d\n", __func__, fh->fip_subcode);
return -1;
}
if (fh->fip_subcode == FIP_VLAN_NOTE_VN2VN)
vn2vn = true;
while (len > 0) {
switch (tlv->tlv_type) {
case FIP_TLV_MAC_ADDR:
memcpy(mac, ((struct fip_tlv_mac_addr *)tlv)->mac_addr,
ETHER_ADDR_LEN);
desc_mask |= VALID_MAC;
break;
/*
* this expects to see the MAC_ADDR TLV first,
* and is broken if not
*/
case FIP_TLV_VLAN:
if (tlv->tlv_len != 1) {
FCM_LOG_ERR(EINVAL, "bad length on VLAN TLV");
break;
}
vid = ntohs(((struct fip_tlv_vlan *)tlv)->vlan);
FCM_LOG_DBG("%s: vid=%d\n", __func__, vid);
if (vid) {
vp = fcm_new_vlan(sa->sll_ifindex, vid, vn2vn);
vp->dcb_required = p->dcb_required;
} else {
/* We received a 0 vlan id. Activate the
* physical port itself
*/
fcp_set_next_action(p, FCP_ACTIVATE_IF);
p->auto_vlan = 0;
}
desc_mask |= VALID_VLAN;
break;
default:
/* unexpected or unrecognized descriptor */
FCM_LOG_DBG("ignoring TLV type %d", tlv->tlv_type);
break;
}
len -= tlv->tlv_len;
tlv = ((void *) tlv) + (tlv->tlv_len << 2);
};
if (desc_mask == (VALID_MAC | VALID_VLAN)) {
/* cancel the retry timer, valid response received */
sa_timer_cancel(&p->vlan_disc_timer);
return 0;
} else {
return -1;
}
}
static void fcm_fip_recv(void *arg)
{
struct fcoe_port *p = arg;
fip_recv(p->fip_socket, fcm_vlan_disc_handler, p);
}
static int fcm_vlan_disc_socket(struct fcoe_port *p)
{
int fd;
int origdev = 1;
fd = fip_socket(p->ifindex, p->mac, FIP_NONE);
if (fd < 0) {
FCM_LOG_ERR(errno, "socket error");
return fd;
}
setsockopt(fd, SOL_PACKET, PACKET_ORIGDEV, &origdev, sizeof(origdev));
sa_select_add_fd(fd, fcm_fip_recv, NULL, NULL, p);
return fd;
}
/* fcm_vlan_dev_real_dev - query vlan real_dev
* @vlan_ifname - vlan device ifname to find real interface name for
* @real_ifname - pointer to copy real ifname to
*
* Make an ioctl call to find the real device for vlan_ifname.
* Copy to real_ifname if found.
*/
static void fcm_vlan_dev_real_dev(char *vlan_ifname, char *real_ifname)
{
int fd;
struct vlan_ioctl_args ifv;
int rc;
real_ifname[0] = '\0';
fd = socket(PF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
FCM_LOG_ERR(errno, "open vlan query socket error");
return;
}
memset(&ifv, 0, sizeof(ifv));
ifv.cmd = GET_VLAN_REALDEV_NAME_CMD;
if (strlen(vlan_ifname) > (sizeof(ifv.device1) - 1)) {
FCM_LOG_ERR(ENOSPC, "no room for vlan ifname");
goto close_fd;
}
rc = snprintf(ifv.device1, IFNAMSIZ, "%s", vlan_ifname);
if (rc < 0 || rc >= IFNAMSIZ)
goto close_fd;
if (ioctl(fd, SIOCGIFVLAN, &ifv) == 0) {
rc = snprintf(real_ifname, IFNAMSIZ, "%s", ifv.u.device2);
if (rc < 0 || rc >= IFNAMSIZ) {
real_ifname[0] = '\0';
goto close_fd;
}
}
close_fd:
close(fd);
}
/* fcm_is_linkinfo_vlan - parse nlmsg linkinfo rtattr for vlan kind
* @ap: pointer to the linkinfo rtattr
*
* This function parses the linkinfo rtattr and returns
* 1 if it is kind vlan otherwise returns 0.
*/
static int fcm_is_linkinfo_vlan(struct rtattr *ap)
{
struct rtattr *info;
int len;
info = (struct rtattr *) (RTA_DATA(ap));
for (len = ap->rta_len; RTA_OK(info, len); info = RTA_NEXT(info, len)) {
if (info->rta_type != IFLA_INFO_KIND)
continue;
if (strncmp("vlan", RTA_DATA(info), sizeof("vlan")))
return 0;
else
return 1;
}
return 0;
}
/* fcm_set_next_action - determine the next action for the FCoE interface
* @p - pointer to the fcoe_port structure for the FCoE interface
* @action - requested next action to take on the FCoE interface
*
* Based on the last_action taken on the FCoE interface and the requested
* next action, the next action field in the FCoE interface's fcoe_port
* structure is set.
* Notes: last_action is initialized to FCP_DESTROY_IF when the fcoe_port is
* created and it is never set to FCP_WAIT.
* The requested action FCP_ACTIVATE_IF is resolved to either
* FCP_CREATE_IF or FCP_ENABLE_IF as appropriate.
*/
static void fcp_set_next_action(struct fcoe_port *p, enum fcp_action action)
{
switch (p->last_action) {
case FCP_CREATE_IF:
switch (action) {
case FCP_DESTROY_IF:
case FCP_ENABLE_IF:
case FCP_DISABLE_IF:
case FCP_RESET_IF:
case FCP_SCAN_IF:
p->action = action;
break;
case FCP_ACTIVATE_IF:
if (p->auto_vlan)
p->action = FCP_VLAN_DISC;
else
p->action = FCP_ENABLE_IF;
break;
default:
p->action = FCP_WAIT;
break;
}
break;
case FCP_DESTROY_IF:
switch (action) {
case FCP_CREATE_IF:
case FCP_ACTIVATE_IF:
case FCP_ENABLE_IF:
if (p->auto_vlan)
p->action = FCP_VLAN_DISC;
else if (p->fcoe_enable)
p->action = FCP_CREATE_IF;
else
p->action = FCP_WAIT;
break;
default:
p->action = FCP_WAIT;
break;
}
break;
case FCP_ENABLE_IF:
switch (action) {
case FCP_DESTROY_IF:
case FCP_DISABLE_IF:
case FCP_RESET_IF:
case FCP_SCAN_IF:
p->action = action;
break;
default:
p->action = FCP_WAIT;
break;
}
break;
case FCP_DISABLE_IF:
switch (action) {
case FCP_DESTROY_IF:
case FCP_RESET_IF:
p->action = action;
break;
case FCP_ENABLE_IF:
case FCP_ACTIVATE_IF:
if (p->auto_vlan)
p->action = FCP_VLAN_DISC;
else
p->action = FCP_ENABLE_IF;
break;
default:
p->action = FCP_WAIT;
break;
}
break;
case FCP_RESET_IF:
case FCP_SCAN_IF:
switch (action) {
case FCP_DESTROY_IF:
case FCP_DISABLE_IF:
case FCP_RESET_IF:
case FCP_SCAN_IF:
p->action = action;
break;
case FCP_ENABLE_IF:
case FCP_ACTIVATE_IF:
if (p->auto_vlan)
p->action = FCP_VLAN_DISC;
else
p->action = FCP_ENABLE_IF;
break;
default:
p->action = FCP_WAIT;
break;
}
break;
case FCP_VLAN_DISC:
switch (action) {
case FCP_ACTIVATE_IF:
if (p->auto_vlan)
p->action = FCP_VLAN_DISC;
else if (p->fcoe_enable)
p->action = FCP_CREATE_IF;
else
p->action = FCP_WAIT;
break;
case FCP_DESTROY_IF:
case FCP_DISABLE_IF:
case FCP_RESET_IF:
case FCP_SCAN_IF:
if (p->fip_socket >= 0) {
sa_timer_cancel(&p->vlan_disc_timer);
sa_select_rem_fd(p->fip_socket);
close(p->fip_socket);
p->fip_socket = -1;
}
p->action = action;
break;
default:
p->action = FCP_WAIT;
break;
}
break;
default:
/* last_action is never set to FCP_WAIT */
break;
}
}
static void fcp_action_set(char *ifname, enum fcp_action action)
{
struct fcoe_port *p;
p = fcm_find_fcoe_port(ifname, FCP_REAL_IFNAME);
while (p) {
if (p->fcoe_enable) {
switch (action) {
case FCP_ACTIVATE_IF:
/*
* let the VLAN discovery code
* enabled auto-VLANs
*/
if (!p->auto_created)
fcp_set_next_action(p, FCP_ACTIVATE_IF);
else
fcp_set_next_action(p, FCP_WAIT);
break;
default:
fcp_set_next_action(p, action);
}
}
p = fcm_find_next_fcoe_port(p, ifname);
}
}
/*
* Send DCB_CMD_IEEE_GET request for an interface.
*/
static void ieee_get_req(struct fcm_netif *ff)
{
int iflen;
int rc;
int seq;
struct {
struct nlmsghdr nl;
struct dcbmsg dcbmsg;
struct rtattr rta;
char ifname[IFNAMSIZ];
} msg;
seq = ++fcm_link_seq;
if (!seq)
seq = ++fcm_link_seq;
iflen = strlen(ff->ifname);
if (iflen >= IFNAMSIZ)
iflen = IFNAMSIZ - 1;
memset(&msg, 0, sizeof(msg));
msg.nl.nlmsg_len = NLMSG_ALIGN(sizeof(msg) - sizeof(msg.ifname) +
iflen + 1);
msg.nl.nlmsg_type = RTM_GETDCB;
msg.nl.nlmsg_flags = NLM_F_REQUEST;
msg.nl.nlmsg_seq = seq;
msg.nl.nlmsg_pid = getpid();
msg.dcbmsg.cmd = DCB_CMD_IEEE_GET;
msg.dcbmsg.dcb_family = AF_UNSPEC;
msg.dcbmsg.dcb_pad = 0;
msg.rta.rta_len = NLMSG_ALIGN(NLA_HDRLEN + iflen + 1);
msg.rta.rta_type = DCB_ATTR_IFNAME;
strncpy(msg.ifname, ff->ifname, iflen);
ff->ieee_resp_pending = seq;
rc = write(fcm_link_socket, &msg, msg.nl.nlmsg_len);
if (rc < 0) {
FCM_LOG_ERR(errno, "%s: %s: write failed\n", __func__,
ff->ifname);
ff->ieee_resp_pending = 0;
}
}
/*
* clear_ieee_info - Clear IEEE info to unknown values
*/
static void clear_ieee_info(struct fcm_netif *ff)
{
ff->ieee_pfc_info = 0;
ff->ieee_app_info = 0;
ff->dcbx_cap = 0;
}
STR_ARR(ieee_states, "Unknown", "Out of range",
[IEEE_INIT] = "IEEE_INIT",
[IEEE_GET_STATE] = "IEEE_GET_STATE",
[IEEE_DONE] = "IEEE_DONE",
[IEEE_ACTIVE] = "IEEE_ACTIVE",
);
static void
ieee_state_set(struct fcm_netif *ff, enum ieee_state new_state)
{
if (ff->ff_operstate != IF_OPER_UP) {
ff->ieee_state = IEEE_INIT;
return;
}
if (fcoe_config.debug) {
FCM_LOG_DEV_DBG(ff, "IEEE state change: %s -> %s",
getstr(&ieee_states, ff->ieee_state),
getstr(&ieee_states, new_state));
}
if (new_state == IEEE_GET_STATE)
clear_ieee_info(ff);
ff->ieee_state = new_state;
ff->ieee_resp_pending = 0;
}
static struct sa_nameval fcm_dcbd_states[] = FCM_DCBD_STATES;
static void fcm_dcbd_state_set(struct fcm_netif *ff,
enum fcm_dcbd_state new_state)
{
if (ff->ff_operstate != IF_OPER_UP) {
ff->ff_dcbd_state = FCD_INIT;
return;
}
if (fcoe_config.debug) {
char old[32];
char new[32];
FCM_LOG_DEV_DBG(ff, "DCBD state change: %s -> %s",
sa_enum_decode(old, sizeof(old),
fcm_dcbd_states,
ff->ff_dcbd_state),
sa_enum_decode(new, sizeof(new),
fcm_dcbd_states, new_state));
}
if (new_state == FCD_GET_DCB_STATE)
clear_dcbd_info(ff);
if (new_state == FCD_INIT) {
ff->dcbd_retry_cnt = 0;
sa_timer_cancel(&ff->dcbd_retry_timer);
}
if (new_state == FCD_ERROR) {
ff->dcbd_retry_cnt++;
FCM_LOG_DEV_DBG(ff, "%s: SETTING lldpad RETRY TIMER = %d\n",
ff->ifname,
ff->dcbd_retry_cnt * DCBD_REQ_RETRY_TIMEOUT);
sa_timer_set(&ff->dcbd_retry_timer,
ff->dcbd_retry_cnt * DCBD_REQ_RETRY_TIMEOUT);
}
ff->ff_dcbd_state = new_state;
ff->response_pending = 0;
}
static int fip_recv_vlan_req(struct sockaddr_ll *ssa, struct fcoe_port *sp)
{
struct fip_tlv_vlan *vlan_tlvs = NULL;
int vlan_count = 0;
struct fcoe_port *p;
int rc;
/* Handle all FCoE ports which are on VLANs over this ifname. */
p = fcm_find_fcoe_port(sp->real_ifname, FCP_REAL_IFNAME);
for (; p; p = fcm_find_next_fcoe_port(p, sp->real_ifname)) {
int vid;
struct fip_tlv_vlan *vtp;
if (!p->ready)
continue;
if (p->mode != FCOE_MODE_VN2VN)
continue;
vid = fcoe_vid_from_ifname(p->ifname);
if (vid < 0)
continue;
vtp = realloc(vlan_tlvs, sizeof(*vlan_tlvs));
if (!vtp)
break;
memset(&vtp[vlan_count], 0, sizeof(*vtp));
vtp[vlan_count].hdr.tlv_type = FIP_TLV_VLAN;
vtp[vlan_count].hdr.tlv_len = 1;
vtp[vlan_count].vlan = htons(vid);
++vlan_count;
vlan_tlvs = vtp;
}
FCM_LOG_DBG("%s: %d vlans found\n", __func__, vlan_count);
rc = fip_send_vlan_notification(sp->fip_responder_socket,
ssa->sll_ifindex, sp->mac,
ssa->sll_addr, vlan_tlvs, vlan_count);
if (rc < 0) {
FCM_LOG_ERR(-rc, "%s: fip_send_vlan_notification error\n",
__func__);
}
if (vlan_tlvs)
free(vlan_tlvs);
return rc;
}
static int fip_vlan_disc_handler(struct fiphdr *fh, struct sockaddr_ll *sa,
void *arg)
{
struct fcoe_port *p = arg;
int rc = -1;
if (ntohs(fh->fip_proto) != FIP_PROTO_VLAN) {
FCM_LOG_DBG("ignoring FIP packet, protocol %d\n",
ntohs(fh->fip_proto));
return -1;
}
switch (fh->fip_subcode) {
case FIP_VLAN_REQ:
FCM_LOG_DBG("received VLAN req, subcode=%d\n", fh->fip_subcode);
rc = fip_recv_vlan_req(sa, p);
break;
default:
FCM_LOG_DBG("ignored FIP VLAN packet with subcode %d\n",
fh->fip_subcode);
break;
}
return rc;
}
static void fip_responder(void *arg)
{
struct fcoe_port *p = arg;
fip_recv(p->fip_responder_socket, fip_vlan_disc_handler, p);
}
static void init_fip_vn2vn_responder(struct fcoe_port *p)
{
int s;
if (p->fip_responder_socket >= 0)
return;
s = fip_socket(p->ifindex, p->mac, FIP_ALL_VN2VN);
if (s < 0) {
FCM_LOG_ERR(errno, "%s: Failed to get fip socket\n", p->ifname);
return;
}
p->fip_responder_socket = s;
FCM_LOG_DBG("%s: Adding FIP responder socket\n", p->ifname);
sa_select_add_fd(s, fip_responder, NULL, NULL, p);
}
static void update_fcoe_port_state(struct fcoe_port *p, unsigned int type,
u_int8_t operstate, enum fcoeport_ifname t)
{
struct fcm_netif *ff = NULL;
if (type != RTM_DELLINK) {
ff = fcm_netif_lookup_create(p->real_ifname);
if (!ff)
return;
/* Only set the ff_operstate field of the network interface
* element if this routine is being called for the real
* network interface, or, if the interface is a VLAN, if the
* network interface element has not been intialized and the
* VLAN operstate is up (if VLAN is up, then real interface is
* up).
*/
if ((t == FCP_REAL_IFNAME) ||
((t == FCP_CFG_IFNAME) &&
(ff->ff_operstate == IF_OPER_UNKNOWN) &&
(operstate == IF_OPER_UP)))
ff->ff_operstate = operstate;
if (t == FCP_REAL_IFNAME && p->fip_resp)
init_fip_vn2vn_responder(p);
if (!p->fcoe_enable) {
fcp_set_next_action(p, FCP_DESTROY_IF);
return;
}
if (operstate == IF_OPER_UP) {
if (p->dcb_required) {
/* If DCB is required, do not start the dcbd
* query sequence if this routine is being
* called for a real interface and the FCoE
* interface is configured on a VLAN.
*/
if (!((t == FCP_REAL_IFNAME) &&
strncmp(p->ifname, p->real_ifname,
IFNAMSIZ))) {
fcm_dcbd_state_set(ff,
FCD_GET_DCB_STATE);
ieee_state_set(ff, IEEE_GET_STATE);
}
} else {
/* hold off on auto-created VLAN ports until
* VLAN discovery can validate that the setup
* has not changed */
if (!p->auto_created || !p->auto_vlan)
fcp_set_next_action(p, FCP_ACTIVATE_IF);
}
} else {
fcp_set_next_action(p, FCP_DISABLE_IF);
}
} else {
fcp_set_next_action(p, FCP_DESTROY_IF);
}
}
static int fcoe_vid_from_ifname(const char *ifname)
{
int vid = -1;
int rc;
if (strlen(ifname) <= strlen(FCOE_VLAN_SUFFIX) ||
strcmp(&ifname[strlen(ifname) - strlen(FCOE_VLAN_SUFFIX)],
FCOE_VLAN_SUFFIX))
return vid;
rc = sscanf(ifname, FCOE_VID_SCAN_FORMAT, &vid);
if (rc == 1)
return vid;
return -1;
}
static void fcm_process_link_msg(struct ifinfomsg *ip, int len, unsigned type)
{
struct fcoe_port *p;
struct rtattr *ap;
char ifname[IFNAMSIZ];
char real_dev[IFNAMSIZ];
u_int8_t operstate;
unsigned char mac[ETHER_ADDR_LEN];
int is_vlan;
int ifindex;
is_vlan = 0;
operstate = IF_OPER_UNKNOWN;
ifindex = ip->ifi_index;
if (ip->ifi_type != ARPHRD_ETHER)
return;
len -= sizeof(*ip);
for (ap = (struct rtattr *)(ip + 1); RTA_OK(ap, len);
ap = RTA_NEXT(ap, len)) {
switch (ap->rta_type) {
case IFLA_ADDRESS:
if (RTA_PAYLOAD(ap) == 6)
memcpy(mac, RTA_DATA(ap), ETHER_ADDR_LEN);
break;
case IFLA_IFNAME:
sa_strncpy_safe(ifname, sizeof(ifname),
RTA_DATA(ap),
RTA_PAYLOAD(ap));
break;
case IFLA_OPERSTATE:
operstate = *(uint8_t *) RTA_DATA(ap);
break;
case IFLA_LINKINFO:
if (fcm_is_linkinfo_vlan(ap))
is_vlan = 1;
break;
default:
break;
}
}
p = fcm_find_fcoe_port(ifname, FCP_CFG_IFNAME);
if (is_vlan) {
/* if not in fcoe port list, then ignore this ifname */
if (!p)
return;
p->ifindex = ifindex;
memcpy(p->mac, mac, ETHER_ADDR_LEN);
/* don't do VLAN discovery on a VLAN */
p->auto_vlan = 0;
/* try to find the real device name */
real_dev[0] = '\0';
fcm_vlan_dev_real_dev(ifname, real_dev);
if (strlen(real_dev)) {
strncpy(p->real_ifname, real_dev, IFNAMSIZ);
p->real_ifname[IFNAMSIZ - 1] = '\0';
}
if (p->ready)
update_fcoe_port_state(p, type, operstate,
FCP_CFG_IFNAME);
p->last_msg_type = type;
} else {
/* the ifname is not a VLAN. handle the case where it has
* an FCoE interface configured on it.
*/
if (p) {
p->ifindex = ifindex;
memcpy(p->mac, mac, ETHER_ADDR_LEN);
strncpy(p->real_ifname, ifname, IFNAMSIZ);
p->real_ifname[IFNAMSIZ - 1] = '\0';
update_fcoe_port_state(p, type, operstate,
FCP_REAL_IFNAME);
}
/* handle all FCoE ports which are on VLANs over this
* ifname.
*/
p = fcm_find_fcoe_port(ifname, FCP_REAL_IFNAME);
for (; p; p = fcm_find_next_fcoe_port(p, ifname)) {
int vid;
if (!p->ready)
continue;
vid = fcoe_vid_from_ifname(p->ifname);
if (vid >= 0 && p->mode == FCOE_MODE_VN2VN) {
struct fcoe_port *vp;
vp = fcm_new_vlan(ifindex, vid, true);
vp->dcb_required = p->dcb_required;
}
update_fcoe_port_state(p, type, operstate,
FCP_REAL_IFNAME);
}
}
}
static struct rtattr *find_nested_attr(struct rtattr *rta, __u16 type)
{
struct rtattr *rta_child;
rta_child = NLA_DATA(rta);
rta = NLA_NEXT(rta);
for (; rta > rta_child; rta_child = NLA_NEXT(rta_child))
if (rta_child->rta_type == type)
return rta_child;
return NULL;
}
static struct rtattr *find_attr(struct nlmsghdr *nlh, __u16 type)
{
struct rtattr *rta;
int len;
rta = (struct rtattr *)(((char *)NLMSG_DATA(nlh)) +
NLMSG_ALIGN(sizeof(struct dcbmsg)));
len = NLMSG_PAYLOAD(nlh, 0) - sizeof(struct dcbmsg);
while (RTA_OK(rta, len)) {
if (rta->rta_type == type)
return rta;
rta = RTA_NEXT(rta, len);
}
return NULL;
}
static int ieee_get_dcbx(struct nlmsghdr *nlh)
{
struct rtattr *rta;
rta = find_attr(nlh, DCB_ATTR_DCBX);
if (!rta)
return -EIO;
return *(__u8 *)NLA_DATA(rta);
}
static int get_pri_mask_from_ieee(struct rtattr *rta, __u8 dcbx_cap)
{
struct rtattr *rta_parent;
struct rtattr *rta_child;
int rval;
__u8 ieee = dcbx_cap & DCB_CAP_DCBX_VER_IEEE;
rta_parent = find_nested_attr(rta, DCB_ATTR_IEEE_APP_TABLE);
if (!rta_parent)
return -EIO;
rta_child = NLA_DATA(rta_parent);
rta_parent = NLA_NEXT(rta_parent);
rval = 0;
for (; rta_parent > rta_child; rta_child = NLA_NEXT(rta_child)) {
struct dcb_app *app;
if (rta_child->rta_type != DCB_ATTR_IEEE_APP)
continue;
app = (struct dcb_app *)NLA_DATA(rta_child);
if (app->protocol != FCOE_ETH_TYPE)
continue;
if (ieee) {
if (app->selector == IEEE_8021QAZ_APP_SEL_ETHERTYPE)
rval |= 1 << app->priority;
} else {
if (app->selector == DCB_APP_IDTYPE_ETHTYPE)
return app->priority;
}
}
return rval;
}
static void fcm_process_ieee_msg(struct nlmsghdr *nlh)
{
struct dcbmsg *d;
struct rtattr *rta_parent;
struct rtattr *rta_child;
struct fcm_netif *ff;
int dcbx_cap;
int pri_mask;
char ifname[IFNAMSIZ];
d = (struct dcbmsg *)NLMSG_DATA(nlh);
if (d->cmd != DCB_CMD_IEEE_GET && d->cmd != DCB_CMD_IEEE_SET) {
FCM_LOG_DBG("Unexpected command type %d\n", d->cmd);
return;
}
rta_parent = (struct rtattr *)(((char *)d) + NLMSG_ALIGN(sizeof(*d)));
if (rta_parent->rta_type != DCB_ATTR_IFNAME)
return;
strncpy(ifname, NLA_DATA(rta_parent), IFNAMSIZ);
ifname[IFNAMSIZ - 1] = '\0';
ff = fcm_netif_lookup_create(ifname);
if (!ff) {
FCM_LOG("Processing IEEE message: %s not found or created\n",
ifname);
return;
}
dcbx_cap = ieee_get_dcbx(nlh);
if (dcbx_cap < 0) {
FCM_LOG("Processing IEEE message: No DCBx capabilities on %s\n",
ifname);
return;
}
ff->dcbx_cap = dcbx_cap;
if (!ff->ff_dcb_state)
ff->ff_dcb_state = !!(dcbx_cap & DCB_CAP_DCBX_VER_IEEE);
if (d->cmd == DCB_CMD_IEEE_SET && !(dcbx_cap & DCB_CAP_DCBX_VER_IEEE)) {
FCM_LOG("Processing IEEE messgae: %s not in IEEE mode\n",
ifname);
}
rta_parent = find_attr(nlh, DCB_ATTR_IEEE);
if (!rta_parent) {
FCM_LOG("Processing IEEE message: No DCB attribute found on %s\n",
ifname);
return;
}
rta_child = find_nested_attr(rta_parent, DCB_ATTR_IEEE_PFC);
if (!rta_child) {
FCM_LOG("Processing IEEE messgae: No PFC attribute found on %s\n",
ifname);
return;
}
struct ieee_pfc *ieee_pfc = (struct ieee_pfc *)NLA_DATA(rta_child);
ff->ieee_pfc_info = ieee_pfc->pfc_en;
pri_mask = get_pri_mask_from_ieee(rta_parent, dcbx_cap);
if (pri_mask < 0) {
FCM_LOG("Processing IEEE message: No Priority mask found on %s\n",
ifname);
return;
}
FCM_LOG_DBG("Processing IEEE message: FCoE Priority mask on %s is 0x%02X\n",
ifname, pri_mask);
ff->ieee_app_info = pri_mask;
if (ff->ieee_state == IEEE_GET_STATE && d->cmd == DCB_CMD_IEEE_GET &&
ff->ieee_resp_pending == nlh->nlmsg_seq)
ieee_state_set(ff, IEEE_DONE);
}
static void fcm_link_recv(UNUSED void *arg)
{
int rc;
char *buf;
struct nlmsghdr *hp;
struct ifinfomsg *ip;
unsigned type;
size_t plen;
size_t rlen;
/* check to make sure our receive buffer is large enough,
* or scale it up as needed */
rc = recv(fcm_link_socket, NULL, 0, MSG_PEEK | MSG_TRUNC);
if (rc > (int)fcm_link_buf_size) {
FCM_LOG_DBG("resizing link buf to %d bytes\n", rc);
void *resize = realloc(fcm_link_buf, rc);
if (resize) {
fcm_link_buf = resize;
fcm_link_buf_size = rc;
} else {
FCM_LOG_ERR(errno, "Failed to allocate link buffer");
}
}
buf = fcm_link_buf;
rc = recv(fcm_link_socket, buf, fcm_link_buf_size, 0);
if (rc <= 0) {
if (rc < 0)
FCM_LOG_ERR(errno, "Error reading from "
"netlink socket with fd %d",
fcm_link_socket);
return;
}
hp = (struct nlmsghdr *)buf;
rlen = rc;
for (hp = (struct nlmsghdr *)buf; NLMSG_OK(hp, rlen);
hp = NLMSG_NEXT(hp, rlen)) {
type = hp->nlmsg_type;
if (hp->nlmsg_type == NLMSG_DONE)
break;
if (hp->nlmsg_type == NLMSG_ERROR) {
rc = log_nlmsg_error(hp, rlen, "nlmsg error");
break;
}
plen = NLMSG_PAYLOAD(hp, 0);
ip = (struct ifinfomsg *)NLMSG_DATA(hp);
if (plen < sizeof(*ip)) {
FCM_LOG("too short (%d) to be a LINK message", rc);
break;
}
switch (type) {
case RTM_NEWLINK:
case RTM_DELLINK:
case RTM_GETLINK:
FCM_LOG_DBG("Link event: %d flags %05X index %d ",
type, ip->ifi_flags, ip->ifi_index);
fcm_process_link_msg(ip, plen, type);
break;
case RTM_GETDCB:
case RTM_SETDCB:
fcm_process_ieee_msg(hp);
break;
default:
FCM_LOG_DBG("%s: Unexpected type %d\n", __func__, type);
break;
}
}
if (rc == -EBUSY) {
FCM_LOG_DBG("%s: netlink returned -EBUSY, retry\n",
__func__);
fcm_link_getlink();
}
}
/*
* Send rt_netlink request for all network interfaces.
*/
static void fcm_link_getlink(void)
{
struct {
struct nlmsghdr nl;
struct ifinfomsg ifi; /* link level specific information,
not dependent on network protocol */
} msg;
int rc;
memset(&msg, 0, sizeof(msg));
msg.nl.nlmsg_len = sizeof(msg);
msg.nl.nlmsg_type = RTM_GETLINK;
msg.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_ATOMIC;
msg.nl.nlmsg_seq = ++fcm_link_seq;
msg.nl.nlmsg_pid = getpid();
msg.ifi.ifi_family = AF_UNSPEC;
msg.ifi.ifi_type = ARPHRD_ETHER;
rc = send(fcm_link_socket, &msg, sizeof(msg), 0);
if (rc < 0)
FCM_LOG_ERR(errno, "send error");
}
static void fcm_fcoe_init(void)
{
if (fcm_read_config_files())
exit(1);
if (force_legacy || access(FCOE_BUS_CREATE, F_OK)) {
FCM_LOG_DBG("Using libfcoe module parameter interfaces\n");
libfcoe_control = &libfcoe_module_tmpl;
} else {
FCM_LOG_DBG("Using /sys/bus/fcoe interfaces\n");
libfcoe_control = &libfcoe_bus_tmpl;
}
}
/*
* Allocate an FCoE interface state structure.
*/
static struct fcm_netif *fcm_netif_alloc(char *ifname)
{
struct fcm_netif *ff;
sigprocmask(SIG_BLOCK, &block_sigset, NULL);
ff = calloc(1, sizeof(*ff));
if (ff) {
snprintf(ff->ifname, sizeof(ff->ifname), "%s", ifname);
ff->ff_operstate = IF_OPER_UNKNOWN;
ff->ff_enabled = 1;
TAILQ_INSERT_TAIL(&fcm_netif_head, ff, ff_list);
} else {
FCM_LOG_ERR(errno, "failed to allocate fcm_netif");
}
sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);
return ff;
}
/*
* Find or create an FCoE network interface by ifname.
* @ifname - interface name to create
*
* This creates a netif interface structure with interface name,
* or if one already exists returns the existing one.
*/
static struct fcm_netif *fcm_netif_lookup_create(char *ifname)
{
struct fcm_netif *ff;
TAILQ_FOREACH(ff, &fcm_netif_head, ff_list) {
if (!strncmp(ifname, ff->ifname, IFNAMSIZ))
return ff;
}
ff = fcm_netif_alloc(ifname);
if (ff != NULL) {
sa_timer_init(&ff->dcbd_retry_timer, fcm_dcbd_retry_timeout,
(void *)ff);
FCM_LOG_DEV_DBG(ff, "Monitoring port %s\n", ifname);
}
return ff;
}
/*
* Find an FCoE interface by name.
*/
static struct fcm_netif *fcm_netif_lookup(char *ifname)
{
struct fcm_netif *curr, *ff = NULL;
TAILQ_FOREACH(curr, &fcm_netif_head, ff_list) {
if (strcmp(curr->ifname, ifname) == 0) {
ff = curr;
break;
}
}
return ff;
}
static void fcm_dcbd_init()
{
fcm_clif->cl_fd = -1; /* not connected */
fcm_clif->cl_ping_pending = 0;
sa_timer_init(&fcm_dcbd_timer, fcm_dcbd_timeout, NULL);
fcm_dcbd_timeout(NULL);
}
static int fcm_dcbd_connect(void)
{
int rc;
int fd;
struct sockaddr_un dest;
struct sockaddr_un *lp;
socklen_t addrlen;
ASSERT(fcm_clif->cl_fd < 0);
fd = socket(PF_UNIX, SOCK_DGRAM, 0);
if (fd < 0) {
FCM_LOG_ERR(errno, "clif socket open failed"); /* XXX */
return 0;
}
lp = &fcm_clif->cl_local;
memset(lp, 0, sizeof(*lp));
lp->sun_family = AF_LOCAL;
lp->sun_path[0] = '\0';
rc = bind(fd, (struct sockaddr *)lp, sizeof(sa_family_t));
if (rc < 0) {
FCM_LOG_ERR(errno, "clif bind failed");
close(fd);
return 0;
}
memset(&dest, 0, sizeof(dest));
dest.sun_family = AF_LOCAL;
dest.sun_path[0] = '\0';
snprintf(&dest.sun_path[1], sizeof(dest.sun_path) - 1,
"%s", LLDP_CLIF_SOCK);
addrlen = sizeof(sa_family_t) + strlen(dest.sun_path + 1) + 1;
rc = connect(fd, (struct sockaddr *)&dest, addrlen);
if (rc < 0) {
FCM_LOG_ERR(errno, "Failed to connect to lldpad");
close(fd);
return 0;
}
fcm_clif->cl_fd = fd;
sa_select_add_fd(fd, fcm_dcbd_rx, NULL, NULL, fcm_clif);
FCM_LOG_DBG("connected to lldpad");
return 1;
}
static void fcm_dcbd_timeout(UNUSED void *arg)
{
if (fcm_clif->cl_ping_pending > 0) {
fcm_dcbd_request("D"); /* DETACH_CMD */
fcm_dcbd_disconnect();
}
if (fcm_clif->cl_fd < 0) {
if (fcm_dcbd_connect())
fcm_dcbd_request("A"); /* ATTACH_CMD: for events */
else
sa_timer_set(&fcm_dcbd_timer, DCBD_CONNECT_TIMEOUT);
} else {
fcm_clif->cl_ping_pending++;
fcm_dcbd_request("P"); /* ping to verify connection */
}
}
static void fcm_dcbd_retry_timeout(void *arg)
{
struct fcm_netif *ff = (struct fcm_netif *)arg;
ASSERT(ff);
FCM_LOG_DBG("%s: lldpad retry TIMEOUT occurred [%d]",
ff->ifname, ff->dcbd_retry_cnt);
fcm_dcbd_state_set(ff, FCD_GET_DCB_STATE);
fcm_netif_advance(ff);
}
static void fcm_dcbd_disconnect(void)
{
if (fcm_clif) {
if (fcm_clif->cl_fd >= 0) {
sa_select_rem_fd(fcm_clif->cl_fd);
close(fcm_clif->cl_fd);
}
fcm_clif->cl_fd = -1; /* mark as disconnected */
fcm_clif->cl_busy = 0;
fcm_clif->cl_ping_pending = 0;
FCM_LOG_DBG("Disconnected from lldpad");
}
}
static void fcm_dcbd_shutdown(void)
{
FCM_LOG_DBG("Shutdown lldpad connection\n");
fcm_dcbd_request("D"); /* DETACH_CMD */
fcm_dcbd_disconnect();
closelog();
}
static void fcm_cleanup(void)
{
struct fcoe_port *curr, *next;
struct fcm_netif *ff, *head;
sigprocmask(SIG_BLOCK, &block_sigset, NULL);
for (curr = fcoe_config.port; curr; curr = next) {
FCM_LOG_DBG("OP: DESTROY %s\n", curr->ifname);
fcm_fcoe_if_action(FCOE_DESTROY, curr->ifname);
next = curr->next;
free(curr);
}
for (head = TAILQ_FIRST(&fcm_netif_head); head; head = ff) {
ff = TAILQ_NEXT(head, ff_list);
TAILQ_REMOVE(&fcm_netif_head, head, ff_list);
free(head);
}
sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);
free(fcm_link_buf);
}
static u_int32_t fcm_get_hex(char *cp, u_int32_t len, char **endptr)
{
u_int32_t hex = 0;
while (len > 0) {
len--;
if (*cp >= '0' && *cp <= '9')
hex = (hex << 4) | (*cp - '0');
else if (*cp >= 'A' && *cp <= 'F')
hex = (hex << 4) | (*cp - 'A' + 10);
else if (*cp >= 'a' && *cp <= 'f')
hex = (hex << 4) | (*cp - 'a' + 10);
else
break;
cp++;
}
*endptr = (len == 0) ? NULL : cp;
return hex;
}
static void fcm_dcbd_rx(void *arg)
{
struct fcm_clif *clif = arg;
cmd_status st;
char buf[128];
size_t len;
int rc;
char *ep;
len = sizeof(buf);
rc = read(clif->cl_fd, buf, sizeof(buf) - 1);
if (rc < 0)
FCM_LOG_ERR(errno, "read");
else if (rc > 0 && rc < (int)sizeof(buf)) {
buf[rc] = '\0';
len = strlen(buf);
ASSERT(len <= rc);
FCM_LOG_DBG("recv '%s', len=%d bytes succeeded", buf, (int)len);
switch (buf[CLIF_RSP_MSG_OFF]) {
case CMD_RESPONSE:
st = fcm_get_hex(buf + CLIF_STAT_OFF, CLIF_STAT_LEN,
&ep);
if (ep != NULL)
FCM_LOG("unexpected response code from lldpad: "
"len %zd buf %s rc %d", len, buf, rc);
else if (st != cmd_success &&
st != cmd_not_applicable &&
st != cmd_device_not_found) {
FCM_LOG("error response from lldpad: "
"error %d len %zd %s",
st, len, buf);
}
fcm_clif->cl_busy = 0;
switch (buf[3]) {
case DCB_CMD:
fcm_dcbd_cmd_resp(buf, st);
break;
case ATTACH_CMD:
break;
case DETACH_CMD:
break;
case PING_CMD:
if (clif->cl_ping_pending > 0)
--clif->cl_ping_pending;
break;
case LEVEL_CMD:
break;
default:
FCM_LOG("Unexpected cmd in response "
"from lldpad: len %zd %s",
len, buf);
break;
}
break;
case EVENT_MSG:
fcm_dcbd_event(buf, len);
break;
default:
FCM_LOG("Unexpected message from lldpad: len %zd buf %s",
len, buf);
break;
}
}
}
/*
* returns: 1 if request was successfully written
* 0 if the write failed
*/
static int fcm_dcbd_request(char *req)
{
size_t len;
int rc;
if (fcm_clif->cl_fd < 0)
return 0;
len = strlen(req);
ASSERT(fcm_clif->cl_busy == 0);
sa_timer_set(&fcm_dcbd_timer, DCBD_CONNECT_TIMEOUT);
fcm_clif->cl_busy = 1;
rc = write(fcm_clif->cl_fd, req, len);
if (rc < 0) {
FCM_LOG_ERR(errno, "Failed write req %s len %zd", req, len);
fcm_clif->cl_busy = 0;
fcm_dcbd_disconnect();
sa_timer_set(&fcm_dcbd_timer, DCBD_CONNECT_RETRY_TIMEOUT);
return 0;
}
if (rc > FCM_PING_REQ_LEN)
FCM_LOG_DBG("sent '%s', rc=%d bytes succeeded", req, rc);
return 1;
}
/*
* Find port for message.
* The port name length starts at len_off for len_len bytes.
* The entire message length is len.
* The pointer to the message pointer is passed in, and updated to point
* past the interface name.
*/
static struct fcm_netif *fcm_dcbd_get_port(char **msgp, size_t len_off,
size_t len_len, size_t len)
{
struct fcm_netif *ff;
u_int32_t if_len;
char *ep;
char *msg;
char ifname[IFNAMSIZ];
msg = *msgp;
if (len_off + len_len >= len)
return NULL;
if_len = fcm_get_hex(msg + len_off, len_len, &ep);
if (ep != NULL) {
FCM_LOG("Parse error on port len: msg %s", msg);
return NULL;
}
if (len_off + len_len + if_len > len) {
FCM_LOG("Invalid port len %d msg %s", if_len, msg);
return NULL;
}
msg += len_off + len_len;
sa_strncpy_safe(ifname, sizeof(ifname), msg, if_len);
*msgp = msg + if_len;
ff = fcm_netif_lookup(ifname);
if (ff == NULL) {
FCM_LOG("ifname '%s' not found", ifname);
}
return ff;
}
/*
* (XXX) Notes:
* This routine is here to help fcm_dcbd_cmd_resp() to pick up
* information of the response packet from the DCBD.
* Returns: 0 on success
* -1 on failure
*/
static int dcb_rsp_parser(struct fcm_netif *ff, char *rsp)
{
int version;
int dcb_cmd;
int feature;
int subtype;
int plen;
int doff;
int i;
int n;
struct feature_info *f_info = NULL;
char buf[20];
feature = hex2int(rsp+DCB_FEATURE_OFF);
dcb_cmd = hex2int(rsp+DCB_CMD_OFF);
version = rsp[DCB_VER_OFF] & 0x0f;
if (version != CLIF_MSG_VERSION) {
FCM_LOG_DEV(ff, "WARNING: Unexpected rsp version %d\n",
version);
return -1;
}
subtype = hex2int(rsp+DCB_SUBTYPE_OFF);
plen = hex2int(rsp+DCB_PORTLEN_OFF);
doff = DCB_PORT_OFF + plen;
switch (feature) {
case FEATURE_DCB:
ff->ff_dcb_state = (*(rsp+doff+CFG_ENABLE) == '1');
return 0;
case FEATURE_PFC:
f_info = &ff->ff_pfc_info;
break;
case FEATURE_APP:
f_info = &ff->ff_app_info;
f_info->subtype = subtype;
break;
default:
return -1;
}
switch (dcb_cmd) {
case CMD_GET_CONFIG:
f_info->enable = (*(rsp+doff+CFG_ENABLE) == '1');
f_info->advertise = (*(rsp+doff+CFG_ADVERTISE) == '1');
f_info->willing = (*(rsp+doff+CFG_WILLING) == '1');
doff += CFG_LEN;
break;
case CMD_GET_OPER:
f_info->op_vers = hex2int(rsp+doff+OPER_OPER_VER);
f_info->op_error = hex2int(rsp+doff+OPER_ERROR);
f_info->op_mode = (*(rsp+doff+OPER_OPER_MODE) == '1');
f_info->syncd = (*(rsp+doff+OPER_SYNCD) == '1');
doff += OPER_LEN;
if (feature == FEATURE_PFC) {
f_info->u.pfcup = 0;
for (i = 0; i < MAX_USER_PRIORITIES; i++) {
if (*(rsp+doff+PFC_UP(i)) == '1')
f_info->u.pfcup |= 1<<i;
}
}
if (feature == FEATURE_APP && subtype == APP_FCOE_STYPE) {
n = hex2int(rsp+doff+APP_LEN);
snprintf(buf, sizeof(buf), "%*.*s\n",
n, n, rsp+doff+APP_DATA);
f_info->u.appcfg = hex2int(buf);
}
break;
}
return 0;
}
/*
* validate_ieee_info - Validation IEEE DCB status for FCoE
*
* Returns: FCP_ACTIVATE_IF - if the dcb netif qualifies for an fcoe interface
* FCP_DESTROY_IF - if the dcb netif should not support fcoe interface
* FCP_ERROR - if dcb configuration has errors
* FCP_WAIT - if dcb criteria is inconclusive
*/
static enum fcp_action validate_ieee_info(struct fcm_netif *ff)
{
if (ff->ieee_pfc_info & ff->ieee_app_info) {
FCM_LOG_DBG("%s: %s: IEEE active and valid\n",
__func__, ff->ifname);
return FCP_ACTIVATE_IF;
}
FCM_LOG_DBG("%s: %s: IEEE active and invalid, pfc=0x%x, app=0x%x\n",
__func__, ff->ifname, ff->ieee_pfc_info, ff->ieee_app_info);
return FCP_WAIT;
}
/*
* validate_dcbd_info - Validating DCBD configuration and status
*
* Returns: FCP_ACTIVATE_IF - if the dcb netif qualifies for an fcoe interface
* FCP_DESTROY_IF - if the dcb netif should not support fcoe interface
* FCP_ERROR - if dcb configuration has errors
* FCP_WAIT - if dcb criteria is inconclusive
*/
static enum fcp_action validate_dcbd_info(struct fcm_netif *ff)
{
int errors = 0;
int dcbon;
dcbon = ff->ff_dcb_state;
if (dcbon && (ff->dcbx_cap & DCB_CAP_DCBX_VER_IEEE))
return validate_ieee_info(ff);
/* check if dcb state qualifies to create the fcoe interface */
if (dcbon &&
ff->ff_app_info.enable &&
ff->ff_pfc_info.enable &&
ff->ff_app_info.op_mode &&
ff->ff_pfc_info.op_mode &&
ff->ff_pfc_info.u.pfcup & ff->ff_app_info.u.appcfg) {
if (dcbon && !ff->ff_app_info.willing) {
FCM_LOG_DEV(ff,
"WARNING: FCoE willing mode is false\n");
errors++;
}
if (dcbon && !ff->ff_app_info.advertise) {
FCM_LOG_DEV(ff,
"WARNING: FCoE advertise mode is false\n");
errors++;
}
if (dcbon && !ff->ff_pfc_info.willing) {
FCM_LOG_DEV(ff,
"WARNING: PFC willing mode is false\n");
errors++;
}
if (dcbon && !ff->ff_pfc_info.advertise) {
FCM_LOG_DEV(ff,
"WARNING: PFC advertise mode is false\n");
errors++;
}
if (errors)
FCM_LOG_DEV_DBG(ff,
"WARNING: DCB may not be configured correctly\n");
else
FCM_LOG_DEV_DBG(ff, "DCB is configured correctly\n");
if (ff->ff_enabled)
return FCP_ACTIVATE_IF;
else {
ff->ff_enabled = 1;
return FCP_ENABLE_IF;
}
}
/* check if dcb state qualifies to destroy the fcoe interface */
if (!dcbon ||
!ff->ff_app_info.enable ||
(ff->ff_app_info.op_mode && ff->ff_pfc_info.op_mode &&
!(ff->ff_pfc_info.u.pfcup & ff->ff_app_info.u.appcfg))) {
if (dcbon && !ff->ff_dcb_state)
FCM_LOG_DEV(ff, "WARNING: DCB is disabled\n");
if (dcbon && !ff->ff_app_info.enable)
FCM_LOG_DEV(ff, "WARNING: FCoE enable is off\n");
if (dcbon &&
!(ff->ff_pfc_info.u.pfcup & ff->ff_app_info.u.appcfg))
FCM_LOG_DEV(ff,
"WARNING: FCoE priority (0x%02x) doesn't "
"intersect with PFC priority (0x%02x)\n",
ff->ff_app_info.u.appcfg,
ff->ff_pfc_info.u.pfcup);
ff->ff_enabled = 0;
return FCP_DISABLE_IF;
}
/* The dcbd state does not match the create or destroy criteria.
* Log possible problems.
*/
if (dcbon && !ff->ff_app_info.willing) {
FCM_LOG_DEV(ff, "WARNING: FCoE willing mode is false\n");
errors++;
}
if (dcbon && !ff->ff_app_info.advertise) {
FCM_LOG_DEV(ff, "WARNING: FCoE advertise mode is false\n");
errors++;
}
if (dcbon && !ff->ff_app_info.op_mode) {
FCM_LOG_DEV(ff, "WARNING: FCoE operational mode is false\n");
print_errors(ff->ff_app_info.op_error);
errors++;
}
if (dcbon && !ff->ff_pfc_info.enable) {
FCM_LOG_DEV(ff, "WARNING: PFC enable is off\n");
errors++;
}
if (dcbon && !ff->ff_pfc_info.advertise) {
FCM_LOG_DEV(ff, "WARNING: PFC advertise mode is false\n");
errors++;
}
if (dcbon && !ff->ff_app_info.op_mode) {
FCM_LOG_DEV(ff, "WARNING: APP:0 operational mode is false\n");
print_errors(ff->ff_app_info.op_error);
errors++;
}
if (dcbon && !ff->ff_pfc_info.op_mode) {
FCM_LOG_DEV(ff, "WARNING: PFC operational mode is false\n");
print_errors(ff->ff_pfc_info.op_error);
errors++;
}
if (dcbon && !(ff->ff_pfc_info.u.pfcup & ff->ff_app_info.u.appcfg)) {
FCM_LOG_DEV(ff, "WARNING: APP:0 priority (0x%02x) doesn't "
"intersect with PFC priority (0x%02x)\n",
ff->ff_app_info.u.appcfg,
ff->ff_pfc_info.u.pfcup);
errors++;
}
if (errors) {
FCM_LOG_DEV(ff, "WARNING: DCB may be configured incorrectly\n");
return FCP_ERROR;
}
return FCP_WAIT;
}
/*
* clear_dcbd_info - clear dcbd info to unknown values
*
*/
static void clear_dcbd_info(struct fcm_netif *ff)
{
memset(&ff->ff_pfc_info, 0, sizeof(struct feature_info));
memset(&ff->ff_app_info, 0, sizeof(struct feature_info));
}
/**
* fcm_dcbd_set_config() - Response handler for set config command
* @ff: fcoe port structure
* @st: status
*/
static void fcm_dcbd_set_config(struct fcm_netif *ff)
{
if (ff->ff_dcbd_state == FCD_SEND_CONF)
fcm_dcbd_state_set(ff, FCD_GET_PFC_CONFIG);
}
/**
* fcm_dcbd_get_config() - Response handler for get config command
* @ff: fcoe port structure
* @resp: response buffer
* @st: status
*/
static void fcm_dcbd_get_config(struct fcm_netif *ff, char *resp)
{
switch (ff->ff_dcbd_state) {
case FCD_GET_DCB_STATE:
if (!dcb_rsp_parser(ff, resp)) {
if (ff->ff_dcb_state &&
!(ff->dcbx_cap & DCB_CAP_DCBX_VER_IEEE))
fcm_dcbd_state_set(ff, FCD_GET_PFC_CONFIG);
else
fcm_dcbd_state_set(ff, FCD_DONE);
} else
fcm_dcbd_state_set(ff, FCD_ERROR);
break;
case FCD_GET_PFC_CONFIG:
if (!dcb_rsp_parser(ff, resp))
fcm_dcbd_state_set(ff, FCD_GET_APP_CONFIG);
else
fcm_dcbd_state_set(ff, FCD_ERROR);
break;
case FCD_GET_APP_CONFIG:
if (!dcb_rsp_parser(ff, resp))
fcm_dcbd_state_set(ff, FCD_GET_PFC_OPER);
else
fcm_dcbd_state_set(ff, FCD_ERROR);
break;
default:
break;
}
}
/**
* fcm_dcbd_get_oper() - Response handler for get operational state command
* @ff: fcoe port structure
* @resp: response buffer
* @cp: response buffer pointer, points past the interface name
* @st: status
*
* Sample msg: R00C103050004eth8010100100208
* opppssll vvmmeemsllpp
*/
static void fcm_dcbd_get_oper(struct fcm_netif *ff, char *resp, char *cp)
{
u_int32_t val;
char *ep = NULL;
val = fcm_get_hex(cp + OPER_ERROR, 2, &ep);
if (ep) {
FCM_LOG_DEV(ff, "Invalid get oper response "
"parse error byte %td, resp %s", ep - cp, cp);
fcm_dcbd_state_set(ff, FCD_ERROR);
} else {
if (val && fcoe_config.debug)
print_errors(val);
switch (ff->ff_dcbd_state) {
case FCD_GET_PFC_OPER:
if (dcb_rsp_parser(ff, resp) || !ff->ff_pfc_info.syncd)
fcm_dcbd_state_set(ff, FCD_ERROR);
else
fcm_dcbd_state_set(ff, FCD_GET_APP_OPER);
FCM_LOG_DEV_DBG(ff, "PFC feature is %ssynced",
ff->ff_pfc_info.syncd ? "" : "not ");
FCM_LOG_DEV_DBG(ff, "PFC operating mode is %s",
ff->ff_pfc_info.op_mode ? "on" :
"off ");
break;
case FCD_GET_APP_OPER:
if (dcb_rsp_parser(ff, resp) || !ff->ff_app_info.syncd)
fcm_dcbd_state_set(ff, FCD_ERROR);
else
fcm_dcbd_state_set(ff, FCD_DONE);
FCM_LOG_DEV_DBG(ff, "FCoE feature is %ssynced",
ff->ff_app_info.syncd ? "" :
"not ");
FCM_LOG_DEV_DBG(ff, "FCoE operating mode is %s",
ff->ff_app_info.op_mode ? "on" :
"off ");
break;
default:
break;
}
}
}
/*
* Handle command response.
* Response buffer points past command code character in response.
*/
static void fcm_dcbd_cmd_resp(char *resp, cmd_status st)
{
struct fcm_netif *ff;
u_int32_t ver;
u_int32_t cmd;
u_int32_t feature;
u_int32_t state;
char *ep;
char *cp;
size_t len;
resp += CLIF_RSP_OFF;
len = strlen(resp);
ver = fcm_get_hex(resp + DCB_VER_OFF, DCB_VER_LEN, &ep);
if (ep != NULL) {
FCM_LOG("parse error: resp %s", resp);
return;
} else if (ver != CLIF_RSP_VERSION) {
FCM_LOG("unexpected version %d resp %s", ver, resp);
return;
}
cmd = fcm_get_hex(resp + DCB_CMD_OFF, DCB_CMD_LEN, &ep);
if (ep != NULL) {
FCM_LOG("parse error on resp cmd: resp %s", resp);
return;
}
feature = fcm_get_hex(resp + DCB_FEATURE_OFF, DCB_FEATURE_LEN, &ep);
if (ep != NULL) {
FCM_LOG("parse error on resp feature: resp %s", resp);
return;
}
fcm_get_hex(resp + DCB_SUBTYPE_OFF, DCB_SUBTYPE_LEN, &ep);
if (ep != NULL) {
FCM_LOG("parse error on resp subtype: resp %s", resp);
return;
}
cp = resp;
ff = fcm_dcbd_get_port(&cp, DCB_PORTLEN_OFF, DCB_PORTLEN_LEN, len);
if (ff == NULL) {
FCM_LOG("port not found. resp %s", resp);
return;
}
/*
* check that dcbd response matches the current dcbd state.
*/
state = ff->ff_dcbd_state;
if (((cmd == CMD_GET_CONFIG) &&
((state == FCD_GET_DCB_STATE && feature == FEATURE_DCB) ||
(state == FCD_GET_PFC_CONFIG && feature == FEATURE_PFC) ||
(state == FCD_GET_APP_CONFIG && feature == FEATURE_APP)))
||
((cmd == CMD_GET_OPER) &&
((state == FCD_GET_PFC_OPER && feature == FEATURE_PFC) ||
(state == FCD_GET_APP_OPER && feature == FEATURE_APP)))) {
/* the response matches the current pending query */
ff->response_pending = 0;
if (st != cmd_success) {
if (st == cmd_not_applicable)
fcm_dcbd_state_set(ff, FCD_DONE);
else
fcm_dcbd_state_set(ff, FCD_ERROR);
return;
}
}
switch (cmd) {
case CMD_SET_CONFIG:
fcm_dcbd_set_config(ff);
break;
case CMD_GET_CONFIG:
fcm_dcbd_get_config(ff, resp);
break;
case CMD_GET_OPER:
fcm_dcbd_get_oper(ff, resp, cp);
break;
default:
FCM_LOG_DEV_DBG(ff, "Unknown cmd 0x%x in response: resp %s",
cmd, resp);
break;
}
}
/*
* Handle incoming DCB event message.
* Example message: E5104eth8050001
*/
static void fcm_dcbd_event(char *msg, size_t len)
{
struct fcm_netif *ff;
struct fcoe_port *p;
u_int32_t feature;
u_int32_t subtype;
char *cp;
char *ep;
if (msg[EV_LEVEL_OFF] != MSG_DCB + '0' || len <= EV_PORT_ID_OFF)
return;
if (msg[EV_VERSION_OFF] != CLIF_EV_VERSION + '0') {
FCM_LOG("Unexpected version in event msg %s", msg);
return;
}
cp = msg;
ff = fcm_dcbd_get_port(&cp, EV_PORT_LEN_OFF, EV_PORT_LEN_LEN, len);
if (ff == NULL)
return;
feature = fcm_get_hex(cp + EV_FEATURE_OFF, 2, &ep);
if (ep != NULL) {
FCM_LOG_DEV_DBG(ff, "Invalid feature code in event msg %s",
msg);
return;
}
/*
* Check if the FCoE ports which use the interface on which the
* dcbd event arrived are configured to require dcb.
*/
p = fcm_find_fcoe_port(ff->ifname, FCP_REAL_IFNAME);
while (p) {
if (p->dcb_required && p->last_msg_type != RTM_DELLINK &&
p->fcoe_enable)
break;
p = fcm_find_next_fcoe_port(p, ff->ifname);
}
/*
* dcb is not required or link was removed, ignore dcbd event
*/
if (!p)
return;
if (ff->ff_operstate != IF_OPER_UP)
return;
switch (feature) {
case FEATURE_PG: /* 'E5204eth2020001' */
FCM_LOG_DEV_DBG(ff, "<Got PG Event>\n");
break;
case FEATURE_PFC: /* 'E5204eth2030011' */
FCM_LOG_DEV_DBG(ff, "<Got PFC Event>\n");
fcm_dcbd_state_set(ff, FCD_GET_DCB_STATE);
break;
case FEATURE_APP: /* 'E5204eth2050011' */
FCM_LOG_DEV_DBG(ff, "<Got APP Event>\n");
subtype = fcm_get_hex(cp + EV_SUBTYPE_OFF, 2, &ep);
if (subtype != APP_FCOE_STYPE) {
FCM_LOG_DEV_DBG(ff, "Unknown application subtype "
"in msg %s", msg);
break;
}
fcm_dcbd_state_set(ff, FCD_GET_DCB_STATE);
break;
default:
FCM_LOG_DEV_DBG(ff, "Unknown feature 0x%x in msg %s",
feature, msg);
break;
}
if (fcoe_config.debug) {
if (cp[EV_OP_MODE_CHG_OFF] == '1')
FCM_LOG_DEV_DBG(ff,
"Operational mode changed");
if (cp[EV_OP_CFG_CHG_OFF] == '1')
FCM_LOG_DEV_DBG(ff,
"Operational config changed");
}
}
/*
* The status is interpreted by the client as an 'enum fcoe_status'.
*/
static void fcm_cli_reply(struct sock_info *r, enum fcoe_status status)
{
char rbuf[MAX_MSGBUF];
snprintf(rbuf, MSG_RBUF, "%d", status);
sendto(r->sock, rbuf, MSG_RBUF, 0, (struct sockaddr *)&(r->from),
r->fromlen);
}
static enum fcoe_status fcm_fcoe_if_action(char *path, char *ifname)
{
FILE *fp = NULL;
enum fcoe_status ret = EFAIL;
fp = fopen(path, "w");
if (!fp) {
FCM_LOG_ERR(errno, "%s: Failed to open path %s\n",
progname, path);
goto err_out;
}
if (EOF == fputs(ifname, fp)) {
FCM_LOG_ERR(errno, "%s: Failed to write %s to path %s.\n",
progname, ifname, path);
goto out;
}
ret = SUCCESS;
out:
fclose(fp);
err_out:
return ret;
}
static void fcm_send_fip_request(const struct fcoe_port *p)
{
int dest;
if (p->mode == FCOE_MODE_VN2VN)
dest = FIP_ALL_VN2VN;
else
dest = FIP_ALL_FCF;
fip_send_vlan_request(p->fip_socket, p->ifindex, p->mac, dest);
}
void fcm_vlan_disc_timeout(void *arg)
{
struct fcoe_port *p = arg;
int s;
FCM_LOG_DBG("%s: VLAN discovery TIMEOUT [%d]",
p->ifname, p->vlan_disc_count);
p->vlan_disc_count++;
if (p->fip_socket < 0) {
s = fcm_vlan_disc_socket(p);
if (s < 0) {
FCM_LOG_ERR(errno, "Could not acquire fip socket.\n");
goto set_timeout;
}
p->fip_socket = s;
p->vlan_disc_count = 1;
}
fcm_send_fip_request(p);
set_timeout:
sa_timer_set(&p->vlan_disc_timer, FCM_VLAN_DISC_TIMEOUT);
}
static int fcm_start_vlan_disc(struct fcoe_port *p)
{
int s;
if (p->fip_socket < 0) {
s = fcm_vlan_disc_socket(p);
if (s < 0) {
/*
* If we can't open the socket set the timeout
* anyways so we will retry sending the fipvlan
* request.
*/
FCM_LOG_ERR(errno, "Failed to open socket, setting VLAN DISC timer.\n");
sa_timer_set(&p->vlan_disc_timer, FCM_VLAN_DISC_TIMEOUT);
return s;
}
p->fip_socket = s;
}
p->vlan_disc_count = 1;
fcm_send_fip_request(p);
sa_timer_set(&p->vlan_disc_timer, FCM_VLAN_DISC_TIMEOUT);
return 0;
}
/*
*
* Input: action = 1 Destroy the FCoE interface
* action = 2 Create the FCoE interface
* action = 3 Reset the interface
*/
static void fcm_fcoe_action(struct fcoe_port *p)
{
struct fcoe_port *vp;
char path[MAX_PATH_LEN];
enum fcoe_status rc = SUCCESS;
switch (p->action) {
case FCP_CREATE_IF:
FCM_LOG_DBG("OP: CREATE %s\n", p->ifname);
rc = libfcoe_control->create(p);
if (rc) {
FCM_LOG_DBG("Failed to create FCoE interface "
"for %s, rc is %d\n", p->ifname, rc);
break;
}
FCM_LOG_DBG("OP: created fchost:%s for %s\n",
p->fchost, p->ifname);
break;
case FCP_DESTROY_IF:
FCM_LOG_DBG("OP: DESTROY %s\n", p->ifname);
if (p->auto_vlan) {
/* destroy all the VLANs */
vp = fcm_find_fcoe_port(p->ifname, FCP_REAL_IFNAME);
while (vp) {
if (vp->auto_created) {
vp->ready = 0;
fcp_set_next_action(vp, FCP_DESTROY_IF);
}
vp = fcm_find_next_fcoe_port(vp, p->ifname);
}
rc = SUCCESS;
break;
}
rc = libfcoe_control->destroy(p);
p->fchost[0] = '\0';
break;
case FCP_ENABLE_IF:
FCM_LOG_DBG("OP: ENABLE %s\n", p->ifname);
rc = libfcoe_control->enable(p);
break;
case FCP_DISABLE_IF:
FCM_LOG_DBG("OP: DISABLE %s\n", p->ifname);
if (p->auto_vlan) {
/* disable all the VLANs */
vp = fcm_find_fcoe_port(p->ifname, FCP_REAL_IFNAME);
while (vp) {
if (vp->auto_created) {
vp->ready = 0;
fcp_set_next_action(vp, FCP_DISABLE_IF);
}
vp = fcm_find_next_fcoe_port(vp, p->ifname);
}
break;
}
rc = libfcoe_control->disable(p);
break;
case FCP_RESET_IF:
FCM_LOG_DBG("OP: RESET %s\n", p->ifname);
if (strlen(p->fchost) <= 0) {
fcm_cli_reply(p->sock_reply, ENOFCHOST);
return;
}
sprintf(path, "%s/%s/issue_lip", SYSFS_FCHOST, p->fchost);
FCM_LOG_DBG("OP: RESET %s\n", path);
rc = fcm_fcoe_if_action(path, "1");
break;
case FCP_SCAN_IF:
FCM_LOG_DBG("OP: SCAN %s\n", p->ifname);
if (strlen(p->fchost) <= 0) {
fcm_cli_reply(p->sock_reply, ENOFCHOST);
return;
}
sprintf(path, "%s/%s/device/scsi_host/%s/scan",
SYSFS_FCHOST, p->fchost, p->fchost);
FCM_LOG_DBG("OP: SCAN %s\n", path);
rc = fcm_fcoe_if_action(path, "- - -");
break;
case FCP_VLAN_DISC:
FCM_LOG_DBG("OP: VLAN DISC %s\n", p->ifname);
rc = fcm_start_vlan_disc(p);
break;
default:
return;
break;
}
if (p->sock_reply) {
fcm_cli_reply(p->sock_reply, rc);
free(p->sock_reply);
p->sock_reply = NULL;
}
p->last_action = p->action;
}
/*
* Called for all ports. For FCoE ports and candidates,
* get IEEE DCBX information and set the next action.
*/
static void fcm_netif_ieee_advance(struct fcm_netif *ff)
{
enum fcp_action action;
ASSERT(ff);
ASSERT(fcm_clif);
if (fcm_clif->cl_busy)
return;
if (ff->ieee_resp_pending)
return;
switch (ff->ieee_state) {
case IEEE_INIT:
break;
case IEEE_GET_STATE:
ieee_get_req(ff);
break;
case IEEE_DONE:
action = validate_ieee_info(ff);
if (action == FCP_ACTIVATE_IF) {
fcp_action_set(ff->ifname, action);
ieee_state_set(ff, IEEE_ACTIVE);
}
break;
case IEEE_ACTIVE:
/* TBD enable and disable if needed in IEEE mode */
break;
default:
break;
}
}
/*
* Called for all ports. For FCoE ports and candidates,
* get information and send to dcbd.
*/
static void fcm_netif_advance(struct fcm_netif *ff)
{
char buf[80], params[30];
ASSERT(ff);
ASSERT(fcm_clif);
if (fcm_clif->cl_busy)
return;
if (ff->response_pending)
return;
if (sa_timer_active(&ff->dcbd_retry_timer))
return;
switch (ff->ff_dcbd_state) {
case FCD_INIT:
case FCD_ERROR:
break;
case FCD_GET_DCB_STATE:
snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s",
DCB_CMD, CLIF_RSP_VERSION,
CMD_GET_CONFIG, FEATURE_DCB, 0,
(u_int) strlen(ff->ifname), ff->ifname);
ff->response_pending = fcm_dcbd_request(buf);
break;
case FCD_SEND_CONF:
snprintf(params, sizeof(params), "%x1%x02",
ff->ff_app_info.enable,
ff->ff_app_info.willing);
snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
DCB_CMD, CLIF_RSP_VERSION,
CMD_SET_CONFIG, FEATURE_APP, APP_FCOE_STYPE,
(u_int) strlen(ff->ifname), ff->ifname, params);
ff->response_pending = fcm_dcbd_request(buf);
break;
case FCD_GET_PFC_CONFIG:
snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
DCB_CMD, CLIF_RSP_VERSION,
CMD_GET_CONFIG, FEATURE_PFC, 0,
(u_int) strlen(ff->ifname), ff->ifname, "");
ff->response_pending = fcm_dcbd_request(buf);
break;
case FCD_GET_APP_CONFIG:
snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
DCB_CMD, CLIF_RSP_VERSION,
CMD_GET_CONFIG, FEATURE_APP, APP_FCOE_STYPE,
(u_int) strlen(ff->ifname), ff->ifname, "");
ff->response_pending = fcm_dcbd_request(buf);
break;
case FCD_GET_PFC_OPER:
snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
DCB_CMD, CLIF_RSP_VERSION,
CMD_GET_OPER, FEATURE_PFC, 0,
(u_int) strlen(ff->ifname), ff->ifname, "");
ff->response_pending = fcm_dcbd_request(buf);
break;
case FCD_GET_APP_OPER:
snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
DCB_CMD, CLIF_RSP_VERSION,
CMD_GET_OPER, FEATURE_APP, APP_FCOE_STYPE,
(u_int) strlen(ff->ifname), ff->ifname, "");
ff->response_pending = fcm_dcbd_request(buf);
break;
case FCD_GET_PEER:
snprintf(buf, sizeof(buf), "%c%x%2.2x%2.2x%2.2x%2.2x%s%s",
DCB_CMD, CLIF_RSP_VERSION,
CMD_GET_PEER, FEATURE_APP, APP_FCOE_STYPE,
(u_int) strlen(ff->ifname), ff->ifname, "");
ff->response_pending = fcm_dcbd_request(buf);
break;
case FCD_DONE:
switch (validate_dcbd_info(ff)) {
case FCP_DESTROY_IF:
fcp_action_set(ff->ifname, FCP_DESTROY_IF);
fcm_dcbd_state_set(ff, FCD_INIT);
break;
case FCP_ENABLE_IF:
fcp_action_set(ff->ifname, FCP_ENABLE_IF);
fcm_dcbd_state_set(ff, FCD_INIT);
break;
case FCP_DISABLE_IF:
fcp_action_set(ff->ifname, FCP_DISABLE_IF);
fcm_dcbd_state_set(ff, FCD_INIT);
break;
case FCP_ACTIVATE_IF:
fcp_action_set(ff->ifname, FCP_ACTIVATE_IF);
fcm_dcbd_state_set(ff, FCD_INIT);
break;
case FCP_ERROR:
fcp_action_set(ff->ifname, FCP_DISABLE_IF);
if (ff->dcbd_retry_cnt < DCBD_MAX_REQ_RETRIES)
fcm_dcbd_state_set(ff, FCD_ERROR);
else
fcm_dcbd_state_set(ff, FCD_INIT);
break;
case FCP_WAIT:
default:
break;
}
break;
default:
break;
}
}
/*
* Run through these steps at the end of each select loop.
* 1. Process list of network interfaces
* - issue next dcbd query action
* - if query sequence is complete - update FCoE port objects
* as necessary with a CREATE or DESTROY next action.
* 2. Process FCoE port list - handle next actions, update states, clean up
*/
static void fcm_handle_changes(void)
{
struct fcm_netif *ff;
struct fcoe_port *p;
/*
* Perform pending actions (dcbd queries) on network interfaces.
*/
TAILQ_FOREACH(ff, &fcm_netif_head, ff_list) {
fcm_netif_advance(ff);
fcm_netif_ieee_advance(ff);
}
/*
* Perform actions on FCoE ports
*/
p = fcoe_config.port;
while (p) {
ff = fcm_netif_lookup(p->real_ifname);
if (!ff) {
if (p->sock_reply) {
fcm_cli_reply(p->sock_reply, ENOETHDEV);
free(p->sock_reply);
p->sock_reply = NULL;
p->action = FCP_WAIT;
}
goto next_port;
}
fcm_fcoe_action(p);
fcp_set_next_action(p, FCP_WAIT);
next_port:
p = p->next;
}
}
static void fcm_usage(void)
{
printf("Usage: %s\n"
"\t [-f|--foreground]\n"
"\t [-d|--debug=yes|no]\n"
"\t [-l|--legacy]\n"
"\t [-s|--syslog=yes|no]\n"
"\t [-v|--version]\n"
"\t [-h|--help]\n\n", progname);
exit(1);
}
static void fcm_dump(void)
{
struct fcoe_port *curr, *next;
struct fcm_netif *ff, *head;
FCM_LOG("*** !!! fcoemon dump begin !!! ***\n");
FCM_LOG("*** Listing of fcoe_port instances ***\n");
for (curr = fcoe_config.port; curr; curr = next) {
FCM_LOG("** ifname: %s\n", curr->ifname);
FCM_LOG("ifindex: %d\n", curr->ifindex);
FCM_LOG("real_ifname: %s\n", curr->real_ifname);
FCM_LOG("fcoe_enable: %d\n", curr->fcoe_enable);
FCM_LOG("dcb_required: %d\n", curr->dcb_required);
FCM_LOG("auto_vlan: %d\n", curr->auto_vlan);
FCM_LOG("auto_created: %d\n", curr->auto_created);
FCM_LOG("ready: %d\n", curr->ready);
FCM_LOG("action: %d\n", curr->action);
FCM_LOG("last_action: %d\n", curr->last_action);
FCM_LOG("last_msg_type: %d\n", curr->last_msg_type);
FCM_LOG("mac: %s\n", curr->mac); //TODO
FCM_LOG("vlan_disc_count: %d\n", curr->vlan_disc_count);
FCM_LOG("fip_socket: %d\n", curr->fip_socket); //TODO
FCM_LOG("fchost: %s\n", curr->fchost);
FCM_LOG("ctlr: %s\n", curr->ctlr);
FCM_LOG("last_fc_event_num: %d\n", curr->last_fc_event_num);
next = curr->next;
}
FCM_LOG("*** Listing of fcm_netif instances ***\n");
for (head = TAILQ_FIRST(&fcm_netif_head); head; head = ff) {
FCM_LOG("** ifname: %s", head->ifname);
FCM_LOG("ff_enabled: %d\n", head->ff_enabled);
FCM_LOG("ff_dcb_state: %d\n", head->ff_dcb_state);
FCM_LOG("dcbx_cap: %d\n", head->dcbx_cap);
FCM_LOG("ieee_state: %d\n", head->ieee_state);
FCM_LOG("ieee_resp_pending: %d\n", head->ieee_resp_pending);
FCM_LOG("ieee_pfc_info: %d\n", head->ieee_pfc_info);
FCM_LOG("ieee_app_info: %d\n", head->ieee_app_info);
FCM_LOG("ff_pfc_info: %d\n", head->ff_pfc_info.op_mode);
FCM_LOG("ff_app_info: %d\n", head->ff_app_info.op_mode);
FCM_LOG("ff_operstate: %d\n", head->ff_operstate);
FCM_LOG("ff_dcb_state: %d\n", head->ff_dcb_state);
FCM_LOG("response_pending: %d\n", head->response_pending);
FCM_LOG("dcbd_retry_cnt: %d\n", head->dcbd_retry_cnt);
ff = TAILQ_NEXT(head, ff_list);
}
FCM_LOG("*** !!! fcoemon dump end !!! ***\n");
}
static void fcm_sig(int sig)
{
switch (sig) {
case SIGUSR1:
fcm_dump();
break;
default:
sa_select_exit(sig);
break;
}
}
/*
* TODO: This routine does too much. It executes a 'cmd'
* and allocates a fcoe_port if one doesn't exist. The
* function name implies that it only does the latter.
*/
static struct fcoe_port *
fcm_port_create(char *ifname, enum clif_flags flags, int cmd)
{
struct fcoe_port *p;
struct fcoe_port *curr;
struct fcm_netif *ff;
p = fcm_find_fcoe_port(ifname, FCP_CFG_IFNAME);
if (p) {
p->ready = 1;
if (!p->fcoe_enable) {
p->fcoe_enable = 1;
fcp_set_next_action(p, cmd);
if (p->dcb_required) {
ff = fcm_netif_lookup(p->real_ifname);
if (!ff)
return p;
fcm_dcbd_state_set(ff, FCD_GET_DCB_STATE);
if (ff->ff_dcbd_state == FCD_GET_DCB_STATE)
fcp_set_next_action(p, FCP_WAIT);
}
} else {
p->fcoe_enable = 1;
fcp_set_next_action(p, cmd);
}
return p;
}
p = alloc_fcoe_port(ifname);
if (!p) {
FCM_LOG_ERR(errno, "fail to allocate fcoe_port %s", ifname);
return NULL;
}
fcm_vlan_dev_real_dev(ifname, p->real_ifname);
if (!strlen(p->real_ifname))
snprintf(p->real_ifname, sizeof(p->real_ifname), "%s", ifname);
p->fcoe_enable = 1;
p->dcb_required = 0;
p->mode = flags & CLIF_FLAGS_MODE_MASK;
fcp_set_next_action(p, cmd);
p->next = NULL;
sigprocmask(SIG_BLOCK, &block_sigset, NULL);
if (!fcoe_config.port)
fcoe_config.port = p;
else {
curr = fcoe_config.port;
while (curr->next)
curr = curr->next;
curr->next = p;
}
sigprocmask(SIG_UNBLOCK, &block_sigset, NULL);
/* check and add the real_ifname to the network interface list */
ff = fcm_netif_lookup_create(p->real_ifname);
if (!ff) {
FCM_LOG_ERR(errno, "fail to allocate fcm_netif %s", ifname);
return NULL;
}
return p;
}
static enum fcoe_status fcm_cli_create(char *ifname, enum clif_flags flags,
struct sock_info **r)
{
struct fcoe_port *p, *vp;
enum fcoe_status rc = EFAIL;
char fchost[FCHOSTBUFLEN];
/* existing fchost already? e.g., from fipvlan -cs */
if (!fcoe_find_fchost(ifname, fchost, FCHOSTBUFLEN)) {
FCM_LOG_DBG("Existing fchost %s found for %s\n",
fchost, ifname);
rc = EFCOECONN;
goto out;
}
p = fcm_find_fcoe_port(ifname, FCP_CFG_IFNAME);
if (p && p->fcoe_enable) {
/* no action needed */
rc = EFCOECONN;
goto out;
}
/* re-enable previous VLANs */
if (p && p->auto_vlan) {
vp = fcm_find_fcoe_port(p->ifname, FCP_REAL_IFNAME);
while (vp) {
if (vp->auto_created)
vp->fcoe_enable = 1;
vp = fcm_find_next_fcoe_port(vp, p->ifname);
}
}
/*
* This looks odd, and could use some improvement. We may
* or may not have found a valid port. fcm_port_create
* will execute the 'cmd' even if it doesn't allocate a
* new port. fcm_port_create should probably be split
* into two routines, one that allocs a new port and one
* that executes the command.
*/
p = fcm_port_create(ifname, flags, FCP_CREATE_IF);
if (!p)
goto out;
rc = SUCCESS;
p->sock_reply = *r;
out:
return rc;
}
static enum fcoe_status fcm_cli_destroy(char *ifname,
struct sock_info **r)
{
struct fcoe_port *p;
p = fcm_find_fcoe_port(ifname, FCP_CFG_IFNAME);
if (p) {
if (p->fcoe_enable) {
p->fcoe_enable = 0;
if (p->last_action == FCP_DESTROY_IF)
return ENOACTION;
fcp_set_next_action(p, FCP_DESTROY_IF);
p->sock_reply = *r;
return SUCCESS;
} else {
/* no action needed */
return ENOACTION;
}
}
FCM_LOG_ERR(errno, "%s is not in port list.\n", ifname);
return EFAIL;
}
static enum fcoe_status fcm_cli_action(char *ifname, int cmd,
struct sock_info **r)
{
struct fcoe_port *p;
p = fcm_find_fcoe_port(ifname, FCP_CFG_IFNAME);
if (p) {
fcp_set_next_action(p, cmd);
p->sock_reply = *r;
return SUCCESS;
}
FCM_LOG_ERR(errno, "%s is not in port list.\n", ifname);
return EFAIL;
}
static struct sock_info *fcm_alloc_reply(struct sockaddr_un *f,
socklen_t flen, int s)
{
static struct sock_info *r;
if (flen > sizeof(*f))
return NULL;
r = (struct sock_info *)malloc(sizeof(struct sock_info));
if (!r) {
FCM_LOG_ERR(errno, "Failed in alloc reply sock info.\n");
return NULL;
}
r->sock = s;
memcpy(&r->from, f, flen);
r->fromlen = flen;
return r;
}
/*
* receive function registered in sa_select_loop
*/
static void fcm_srv_receive(void *arg)
{
struct fcm_srv_info *srv_info = arg;
struct clif_data *data;
struct sockaddr_un from;
socklen_t fromlen = sizeof(struct sockaddr_un);
struct sock_info *reply = NULL;
char buf[MAX_MSGBUF], rbuf[MAX_MSGBUF];
char ifname[IFNAMSIZ + 1];
enum fcoe_status rc = EFAIL;
int res, cmd, snum;
size_t size;
snum = srv_info->srv_sock;
res = recvfrom(snum, buf, sizeof(buf) - 1,
MSG_DONTWAIT, (struct sockaddr *)&from, &fromlen);
if (res < 0) {
FCM_LOG_ERR(errno, "Failed to receive data from socket %d",
snum);
return;
}
data = (struct clif_data *)buf;
size = res;
if (size < sizeof(*data)) {
if (size < sizeof(*data) - sizeof(data->flags)) {
FCM_LOG_ERR(EMSGSIZE,
"Message too short from socket %d", snum);
rc = EBADCLIFMSG;
goto err;
}
data->flags = 0;
} else if (size > sizeof(*data)) {
FCM_LOG_ERR(EMSGSIZE, "Message too long from socket %d", snum);
rc = EBADCLIFMSG;
goto err;
}
cmd = data->cmd;
strncpy(ifname, data->ifname, IFNAMSIZ);
ifname[sizeof(data->ifname)] = 0;
if (cmd != CLIF_PID_CMD) {
rc = fcoe_validate_interface(ifname);
if (rc)
goto err;
}
reply = fcm_alloc_reply(&from, fromlen, snum);
if (!reply)
goto err_out;
switch (cmd) {
case CLIF_CREATE_CMD:
FCM_LOG_DBG("Received command to create %s\n", ifname);
rc = fcm_cli_create(ifname, data->flags, &reply);
if (rc)
goto err_out;
break;
case CLIF_DESTROY_CMD:
FCM_LOG_DBG("Received command to destroy %s\n", ifname);
rc = fcm_cli_destroy(ifname, &reply);
if (rc)
goto err_out;
break;
case CLIF_RESET_CMD:
FCM_LOG_DBG("Received command to reset %s\n", ifname);
rc = fcm_cli_action(ifname, FCP_RESET_IF, &reply);
if (rc)
goto err_out;
break;
case CLIF_SCAN_CMD:
FCM_LOG_DBG("Received command to scan %s\n", ifname);
rc = fcm_cli_action(ifname, FCP_SCAN_IF, &reply);
if (rc)
goto err_out;
break;
case CLIF_PID_CMD:
FCM_LOG_DBG("FCMON PID\n");
snprintf(rbuf, MAX_MSGBUF, "%lu", (long unsigned int)getpid());
sendto(snum, rbuf, strlen(rbuf), 0, (struct sockaddr *)&from,
fromlen);
break;
default:
FCM_LOG_DBG("Received invalid command %d for %s\n",
cmd, ifname);
goto err_out;
}
return;
err_out:
free(reply);
err:
snprintf(rbuf, MSG_RBUF, "%d", rc);
sendto(snum, rbuf, MSG_RBUF, 0, (struct sockaddr *)&from, fromlen);
}
static int fcm_systemd_socket(void)
{
char *env, *ptr;
unsigned int p, l;
env = getenv("LISTEN_PID");
if (!env)
return -1;
p = strtoul(env, &ptr, 10);
if (ptr && ptr == env) {
FCM_LOG_DBG("Invalid value '%s' for LISTEN_PID\n", env);
return -1;
}
if ((pid_t)p != getpid()) {
FCM_LOG_DBG("Invalid PID '%d' from LISTEN_PID\n", p);
return -1;
}
env = getenv("LISTEN_FDS");
if (!env) {
FCM_LOG_DBG("LISTEN_FDS is not set\n");
return -1;
}
l = strtoul(env, &ptr, 10);
if (ptr && ptr == env) {
FCM_LOG_DBG("Invalid value '%s' for LISTEN_FDS\n", env);
return -1;
}
if (l != 1) {
FCM_LOG_DBG("LISTEN_FDS specified %d fds\n", l);
return -1;
}
/* systemd returns fds with an offset of '3' */
return 3;
}
static int fcm_srv_create(struct fcm_srv_info *srv_info)
{
socklen_t addrlen;
struct sockaddr_un addr;
int rc = 0;
srv_info->srv_sock = fcm_systemd_socket();
if (srv_info->srv_sock > 0) {
FCM_LOG_DBG("Using systemd socket\n");
goto out_done;
}
srv_info->srv_sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
if (srv_info->srv_sock < 0) {
FCM_LOG_ERR(errno, "Failed to create socket\n");
rc = errno;
goto err;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_LOCAL;
addr.sun_path[0] = '\0';
snprintf(&addr.sun_path[1], sizeof(addr.sun_path) -1,
"%s", CLIF_IFNAME);
addrlen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1;
if (bind(srv_info->srv_sock, (struct sockaddr *)&addr, addrlen) < 0) {
FCM_LOG_ERR(errno, "Failed to bind socket\n");
rc = errno;
goto err_close;
}
out_done:
sa_select_add_fd(srv_info->srv_sock, fcm_srv_receive,
NULL, NULL, srv_info);
FCM_LOG_DBG("Successfully created socket, socket file and binding\n");
return rc;
err_close:
close(srv_info->srv_sock);
err:
return rc;
}
static void fcm_srv_destroy(struct fcm_srv_info *srv_info)
{
FCM_LOG_DBG("Shutdown fcmon server");
close(srv_info->srv_sock);
}
int main(int argc, char **argv)
{
struct fcm_srv_info srv_info;
struct sigaction sig;
int fcm_fg = 0;
int rc;
int c;
memset(&fcoe_config, 0, sizeof(fcoe_config));
strncpy(progname, basename(argv[0]), sizeof(progname));
progname[sizeof(progname) - 1] = '\0';
sa_log_prefix = progname;
sa_log_flags = 0;
openlog(sa_log_prefix, LOG_CONS, LOG_DAEMON);
while ((c = getopt_long(argc, argv, "fd:hls:v",
fcm_options, NULL)) != -1) {
switch (c) {
case 'f':
fcm_fg = 1;
break;
case 'd':
if (!strncmp(optarg, "yes", 3) ||
!strncmp(optarg, "YES", 3)) {
fcoe_config.debug = 1;
enable_debug_log(1);
}
break;
case 'l':
force_legacy = true;
break;
case 's':
if (!strncmp(optarg, "yes", 3) ||
!strncmp(optarg, "YES", 3)) {
fcoe_config.use_syslog = 1;
enable_syslog(1);
}
break;
case 'v':
printf("%s\n", FCOE_UTILS_VERSION);
return 0;
case 'h':
default:
fcm_usage();
break;
}
}
if (argc != optind)
fcm_usage();
umask(0);
/*
* Set up for signals.
*/
memset(&sig, 0, sizeof(sig));
sig.sa_handler = fcm_sig;
/*
* SIGUSR1 will walk the fcoe_port and fcm_netif lists,
* therefore we need to disable this interrupt when we
* do any list manipulations. The 'block_sigset' is the
* set of signals that will be blocked before list
* manipulations on either of the aforementioned lists.
*/
sigemptyset(&block_sigset);
sigaddset(&block_sigset, SIGUSR1);
rc = sigaction(SIGINT, &sig, NULL);
if (rc < 0) {
FCM_LOG_ERR(errno, "Failed to register handler for SIGINT");
exit(1);
}
rc = sigaction(SIGTERM, &sig, NULL);
if (rc < 0) {
FCM_LOG_ERR(errno, "Failed to register handler for SIGTERM");
exit(1);
}
rc = sigaction(SIGHUP, &sig, NULL);
if (rc < 0) {
FCM_LOG_ERR(errno, "Failed to register handler for SIGHUP");
exit(1);
}
rc = sigaction(SIGUSR1, &sig, NULL);
if (rc < 0) {
FCM_LOG_ERR(errno, "Failed to register handler for SIGUSR1");
exit(1);
}
/* check fcoe module */
if (fcoe_checkdir(SYSFS_FCOE)) {
FCM_LOG_ERR(errno, "make sure FCoE driver module is loaded!");
exit(1);
}
fcm_fcoe_init();
rc = fcm_fc_events_init();
if (rc != 0)
exit(1);
rc = fcm_link_init(); /* NETLINK_ROUTE protocol */
if (rc != 0)
goto err_cleanup;
if (fcoe_config.dcb_init)
fcm_dcbd_init();
rc = fcm_srv_create(&srv_info);
if (rc == EADDRINUSE) {
FCM_LOG("Daemon already running OR Failed to bind socket so exiting!\n");
exit(1);
}
if (rc != 0)
goto err_cleanup;
if (!fcm_fg && daemon(0, !fcoe_config.use_syslog)) {
FCM_LOG("Starting daemon failed");
goto err_cleanup;
}
sa_select_set_callback(fcm_handle_changes);
rc = sa_select_loop();
if (rc < 0) {
FCM_LOG_ERR(rc, "select error\n");
goto err_cleanup;
}
fcm_dcbd_shutdown();
fcm_srv_destroy(&srv_info);
if (rc == SIGHUP)
fcm_cleanup();
return 0;
err_cleanup:
fcm_cleanup();
exit(1);
}
/*******************************************************
* The following are debug routines *
*******************************************************/
static void add_msg_to_buf(char *buf, size_t maxlen, char *msg, char *prefix)
{
size_t len = strlen(buf);
if (len + strlen(msg) + strlen(prefix) < maxlen)
sprintf(buf+len, "%s%s", prefix, msg);
}
static void print_errors(int errors)
{
char msg[256];
int cnt = 0;
memset(msg, 0, sizeof(msg));
sprintf(msg, "0x%02x - ", errors);
if (errors & 0x01)
add_msg_to_buf(msg, sizeof(msg), "mismatch with peer",
(cnt++) ? ", " : "");
if (errors & 0x02)
add_msg_to_buf(msg, sizeof(msg), "local configuration error",
(cnt++) ? ", " : "");
if (errors & 0x04)
add_msg_to_buf(msg, sizeof(msg), "multiple TLV's received",
(cnt++) ? ", " : "");
if (errors & 0x08)
add_msg_to_buf(msg, sizeof(msg), "peer error",
(cnt++) ? ", " : "");
if (errors & 0x10)
add_msg_to_buf(msg, sizeof(msg), "multiple LLDP neighbors",
(cnt++) ? ", " : "");
if (errors & 0x20)
add_msg_to_buf(msg, sizeof(msg), "peer feature not present",
(cnt++) ? ", " : "");
if (!errors)
add_msg_to_buf(msg, sizeof(msg), "none", "");
FCM_LOG("%s\n", msg);
}