libdlaggr.c revision 8de9d09562aa1c2ecd5453f23d9eb21db41d9ee7
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stropts.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <strings.h>
#include <libintl.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <libdllink.h>
#include <libdlvlan.h>
#include <libdlaggr.h>
#include <libdladm_impl.h>
/*
* Link Aggregation Administration Library.
*
* This library is used by administration tools such as dladm(1M) to
* configure link aggregations.
*/
#define DLADM_AGGR_DEV "/devices/pseudo/aggr@0:" AGGR_DEVNAME_CTL
/* Limits on buffer size for LAIOC_INFO request */
#define MIN_INFO_SIZE (4*1024)
#define MAX_INFO_SIZE (128*1024)
static uchar_t zero_mac[] = {0, 0, 0, 0, 0, 0};
#define VALID_PORT_MAC(mac) \
(((mac) != NULL) && (bcmp(zero_mac, (mac), ETHERADDRL) != 0) && \
(!(mac)[0] & 0x01))
#define PORT_DELIMITER '.'
#define WRITE_PORT(portstr, portid, size) { \
char pstr[LINKID_STR_WIDTH + 2]; \
(void) snprintf(pstr, LINKID_STR_WIDTH + 2, "%d%c", \
(portid), PORT_DELIMITER); \
(void) strlcat((portstr), pstr, (size)); \
}
#define READ_PORT(portstr, portid, status) { \
errno = 0; \
(status) = DLADM_STATUS_OK; \
(portid) = (int)strtol((portstr), &(portstr), 10); \
if (errno != 0 || *(portstr) != PORT_DELIMITER) { \
(status) = DLADM_STATUS_REPOSITORYINVAL; \
} else { \
/* Skip the delimiter. */ \
(portstr)++; \
} \
}
typedef struct dladm_aggr_modify_attr {
uint32_t ld_policy;
boolean_t ld_mac_fixed;
uchar_t ld_mac[ETHERADDRL];
aggr_lacp_mode_t ld_lacp_mode;
aggr_lacp_timer_t ld_lacp_timer;
} dladm_aggr_modify_attr_t;
typedef struct policy_s {
char *pol_name;
uint32_t policy;
} policy_t;
static policy_t policies[] = {
{"L2", AGGR_POLICY_L2},
{"L3", AGGR_POLICY_L3},
{"L4", AGGR_POLICY_L4}};
#define NPOLICIES (sizeof (policies) / sizeof (policy_t))
typedef struct dladm_aggr_lacpmode_s {
char *mode_str;
aggr_lacp_mode_t mode_id;
} dladm_aggr_lacpmode_t;
static dladm_aggr_lacpmode_t lacp_modes[] = {
{"off", AGGR_LACP_OFF},
{"active", AGGR_LACP_ACTIVE},
{"passive", AGGR_LACP_PASSIVE}};
#define NLACP_MODES (sizeof (lacp_modes) / sizeof (dladm_aggr_lacpmode_t))
typedef struct dladm_aggr_lacptimer_s {
char *lt_str;
aggr_lacp_timer_t lt_id;
} dladm_aggr_lacptimer_t;
static dladm_aggr_lacptimer_t lacp_timers[] = {
{"short", AGGR_LACP_TIMER_SHORT},
{"long", AGGR_LACP_TIMER_LONG}};
#define NLACP_TIMERS (sizeof (lacp_timers) / sizeof (dladm_aggr_lacptimer_t))
typedef struct dladm_aggr_port_state {
char *state_str;
aggr_port_state_t state_id;
} dladm_aggr_port_state_t;
static dladm_aggr_port_state_t port_states[] = {
{"standby", AGGR_PORT_STATE_STANDBY },
{"attached", AGGR_PORT_STATE_ATTACHED }
};
#define NPORT_STATES \
(sizeof (port_states) / sizeof (dladm_aggr_port_state_t))
static int
i_dladm_aggr_strioctl(int cmd, void *ptr, int ilen)
{
int err, fd;
if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0)
return (-1);
err = i_dladm_ioctl(fd, cmd, ptr, ilen);
(void) close(fd);
return (err);
}
/*
* Caller must free attr.lg_ports. The ptr pointer is advanced while convert
* the laioc_info_t to the dladm_aggr_grp_attr_t structure.
*/
static int
i_dladm_aggr_iocp2grpattr(void **ptr, dladm_aggr_grp_attr_t *attrp)
{
laioc_info_group_t *grp;
laioc_info_port_t *port;
int i;
void *where = (*ptr);
grp = (laioc_info_group_t *)where;
attrp->lg_linkid = grp->lg_linkid;
attrp->lg_key = grp->lg_key;
attrp->lg_nports = grp->lg_nports;
attrp->lg_policy = grp->lg_policy;
attrp->lg_lacp_mode = grp->lg_lacp_mode;
attrp->lg_lacp_timer = grp->lg_lacp_timer;
attrp->lg_force = grp->lg_force;
bcopy(grp->lg_mac, attrp->lg_mac, ETHERADDRL);
attrp->lg_mac_fixed = grp->lg_mac_fixed;
if ((attrp->lg_ports = malloc(grp->lg_nports *
sizeof (dladm_aggr_port_attr_t))) == NULL) {
errno = ENOMEM;
goto fail;
}
where = (grp + 1);
/*
* Go through each port that is part of the group.
*/
for (i = 0; i < grp->lg_nports; i++) {
port = (laioc_info_port_t *)where;
attrp->lg_ports[i].lp_linkid = port->lp_linkid;
bcopy(port->lp_mac, attrp->lg_ports[i].lp_mac, ETHERADDRL);
attrp->lg_ports[i].lp_state = port->lp_state;
attrp->lg_ports[i].lp_lacp_state = port->lp_lacp_state;
where = (port + 1);
}
*ptr = where;
return (0);
fail:
return (-1);
}
/*
* Get active configuration of a specific aggregation.
* Caller must free attrp->la_ports.
*/
static dladm_status_t
i_dladm_aggr_info_active(datalink_id_t linkid, dladm_aggr_grp_attr_t *attrp)
{
laioc_info_t *ioc;
int rc, bufsize;
void *where;
dladm_status_t status = DLADM_STATUS_OK;
bufsize = MIN_INFO_SIZE;
ioc = (laioc_info_t *)calloc(1, bufsize);
if (ioc == NULL)
return (DLADM_STATUS_NOMEM);
ioc->li_group_linkid = linkid;
tryagain:
rc = i_dladm_aggr_strioctl(LAIOC_INFO, ioc, bufsize);
if (rc != 0) {
if (errno == ENOSPC) {
/*
* The LAIOC_INFO call failed due to a short
* buffer. Reallocate the buffer and try again.
*/
bufsize *= 2;
if (bufsize <= MAX_INFO_SIZE) {
ioc = (laioc_info_t *)realloc(ioc, bufsize);
if (ioc != NULL) {
bzero(ioc, sizeof (bufsize));
goto tryagain;
}
}
}
status = dladm_errno2status(errno);
goto bail;
}
/*
* Go through each group returned by the aggregation driver.
*/
where = (char *)(ioc + 1);
if (i_dladm_aggr_iocp2grpattr(&where, attrp) != 0) {
status = dladm_errno2status(errno);
goto bail;
}
bail:
free(ioc);
return (status);
}
/*
* Get configuration information of a specific aggregation.
* Caller must free attrp->la_ports.
*/
static dladm_status_t
i_dladm_aggr_info_persist(datalink_id_t linkid, dladm_aggr_grp_attr_t *attrp)
{
dladm_conf_t conf;
uint32_t nports, i;
char *portstr, *next;
dladm_status_t status;
uint64_t u64;
int size;
char macstr[ETHERADDRL * 3];
attrp->lg_linkid = linkid;
if ((status = dladm_read_conf(linkid, &conf)) != DLADM_STATUS_OK)
return (status);
status = dladm_get_conf_field(conf, FKEY, &u64, sizeof (u64));
if (status != DLADM_STATUS_OK)
goto done;
attrp->lg_key = (uint16_t)u64;
status = dladm_get_conf_field(conf, FPOLICY, &u64, sizeof (u64));
if (status != DLADM_STATUS_OK)
goto done;
attrp->lg_policy = (uint32_t)u64;
status = dladm_get_conf_field(conf, FFIXMACADDR, &attrp->lg_mac_fixed,
sizeof (boolean_t));
if (status != DLADM_STATUS_OK)
goto done;
if (attrp->lg_mac_fixed) {
boolean_t fixed;
if ((status = dladm_get_conf_field(conf, FMACADDR, macstr,
sizeof (macstr))) != DLADM_STATUS_OK) {
goto done;
}
if (!dladm_aggr_str2macaddr(macstr, &fixed, attrp->lg_mac)) {
status = DLADM_STATUS_REPOSITORYINVAL;
goto done;
}
}
status = dladm_get_conf_field(conf, FFORCE, &attrp->lg_force,
sizeof (boolean_t));
if (status != DLADM_STATUS_OK)
goto done;
status = dladm_get_conf_field(conf, FLACPMODE, &u64, sizeof (u64));
if (status != DLADM_STATUS_OK)
goto done;
attrp->lg_lacp_mode = (aggr_lacp_mode_t)u64;
status = dladm_get_conf_field(conf, FLACPTIMER, &u64, sizeof (u64));
if (status != DLADM_STATUS_OK)
goto done;
attrp->lg_lacp_timer = (aggr_lacp_timer_t)u64;
status = dladm_get_conf_field(conf, FNPORTS, &u64, sizeof (u64));
if (status != DLADM_STATUS_OK)
goto done;
nports = (uint32_t)u64;
attrp->lg_nports = nports;
size = nports * (LINKID_STR_WIDTH + 1) + 1;
if ((portstr = calloc(1, size)) == NULL) {
status = DLADM_STATUS_NOMEM;
goto done;
}
status = dladm_get_conf_field(conf, FPORTS, portstr, size);
if (status != DLADM_STATUS_OK) {
free(portstr);
goto done;
}
if ((attrp->lg_ports = malloc(nports *
sizeof (dladm_aggr_port_attr_t))) == NULL) {
free(portstr);
status = DLADM_STATUS_NOMEM;
goto done;
}
for (next = portstr, i = 0; i < nports; i++) {
READ_PORT(next, attrp->lg_ports[i].lp_linkid, status);
if (status != DLADM_STATUS_OK) {
free(portstr);
free(attrp->lg_ports);
goto done;
}
}
free(portstr);
done:
dladm_destroy_conf(conf);
return (status);
}
dladm_status_t
dladm_aggr_info(datalink_id_t linkid, dladm_aggr_grp_attr_t *attrp,
uint32_t flags)
{
assert(flags == DLADM_OPT_ACTIVE || flags == DLADM_OPT_PERSIST);
if (flags == DLADM_OPT_ACTIVE)
return (i_dladm_aggr_info_active(linkid, attrp));
else
return (i_dladm_aggr_info_persist(linkid, attrp));
}
/*
* Add or remove one or more ports to/from an existing link aggregation.
*/
static dladm_status_t
i_dladm_aggr_add_rmv(datalink_id_t linkid, uint32_t nports,
dladm_aggr_port_attr_db_t *ports, uint32_t flags, int cmd)
{
char *orig_portstr = NULL, *portstr = NULL;
laioc_add_rem_t *iocp = NULL;
laioc_port_t *ioc_ports;
uint32_t orig_nports, result_nports, len, i, j;
dladm_conf_t conf;
datalink_class_t class;
dladm_status_t status = DLADM_STATUS_OK;
int size;
uint64_t u64;
uint32_t media;
if (nports == 0)
return (DLADM_STATUS_BADARG);
/*
* Sanity check - aggregations can only be created over Ethernet
* physical links.
*/
for (i = 0; i < nports; i++) {
if ((dladm_datalink_id2info(ports[i].lp_linkid, NULL,
&class, &media, NULL, 0) != DLADM_STATUS_OK) ||
(class != DATALINK_CLASS_PHYS) || (media != DL_ETHER)) {
return (DLADM_STATUS_BADARG);
}
}
/*
* First, update the persistent configuration if requested. We only
* need to update the FPORTS and FNPORTS fields of this aggregation.
* Note that FPORTS is a list of port linkids separated by
* PORT_DELIMITER ('.').
*/
if (flags & DLADM_OPT_PERSIST) {
status = dladm_read_conf(linkid, &conf);
if (status != DLADM_STATUS_OK)
return (status);
/*
* Get the original configuration of FNPORTS and FPORTS.
*/
status = dladm_get_conf_field(conf, FNPORTS, &u64,
sizeof (u64));
if (status != DLADM_STATUS_OK)
goto destroyconf;
orig_nports = (uint32_t)u64;
/*
* At least one port needs to be in the aggregation.
*/
if ((cmd == LAIOC_REMOVE) && (orig_nports <= nports)) {
status = DLADM_STATUS_BADARG;
goto destroyconf;
}
size = orig_nports * (LINKID_STR_WIDTH + 1) + 1;
if ((orig_portstr = calloc(1, size)) == NULL) {
status = dladm_errno2status(errno);
goto destroyconf;
}
status = dladm_get_conf_field(conf, FPORTS, orig_portstr, size);
if (status != DLADM_STATUS_OK)
goto destroyconf;
result_nports = (cmd == LAIOC_ADD) ? orig_nports + nports :
orig_nports;
size = result_nports * (LINKID_STR_WIDTH + 1) + 1;
if ((portstr = calloc(1, size)) == NULL) {
status = dladm_errno2status(errno);
goto destroyconf;
}
/*
* get the new configuration and set to result_nports and
* portstr.
*/
if (cmd == LAIOC_ADD) {
(void) strlcpy(portstr, orig_portstr, size);
for (i = 0; i < nports; i++)
WRITE_PORT(portstr, ports[i].lp_linkid, size);
} else {
char *next;
datalink_id_t portid;
uint32_t remove = 0;
for (next = orig_portstr, j = 0; j < orig_nports; j++) {
/*
* Read the portids from the old configuration
* one by one.
*/
READ_PORT(next, portid, status);
if (status != DLADM_STATUS_OK) {
free(portstr);
goto destroyconf;
}
/*
* See whether this port is in the removal
* list. If not, copy to the new config.
*/
for (i = 0; i < nports; i++) {
if (ports[i].lp_linkid == portid)
break;
}
if (i == nports) {
WRITE_PORT(portstr, portid, size);
} else {
remove++;
}
}
if (remove != nports) {
status = DLADM_STATUS_LINKINVAL;
free(portstr);
goto destroyconf;
}
result_nports -= nports;
}
u64 = result_nports;
if ((status = dladm_set_conf_field(conf, FNPORTS,
DLADM_TYPE_UINT64, &u64)) != DLADM_STATUS_OK) {
free(portstr);
goto destroyconf;
}
status = dladm_set_conf_field(conf, FPORTS, DLADM_TYPE_STR,
portstr);
free(portstr);
if (status != DLADM_STATUS_OK)
goto destroyconf;
/*
* Write the new configuration to the persistent repository.
*/
status = dladm_write_conf(conf);
destroyconf:
dladm_destroy_conf(conf);
if (status != DLADM_STATUS_OK) {
free(orig_portstr);
return (status);
}
}
/*
* If the caller only requested to update the persistent
* configuration, we are done.
*/
if (!(flags & DLADM_OPT_ACTIVE))
goto done;
/*
* Update the active configuration.
*/
len = sizeof (*iocp) + nports * sizeof (laioc_port_t);
if ((iocp = malloc(len)) == NULL) {
status = DLADM_STATUS_NOMEM;
goto done;
}
iocp->la_linkid = linkid;
iocp->la_nports = nports;
if (cmd == LAIOC_ADD)
iocp->la_force = (flags & DLADM_OPT_FORCE);
ioc_ports = (laioc_port_t *)(iocp + 1);
for (i = 0; i < nports; i++)
ioc_ports[i].lp_linkid = ports[i].lp_linkid;
if (i_dladm_aggr_strioctl(cmd, iocp, len) < 0)
status = dladm_errno2status(errno);
done:
free(iocp);
/*
* If the active configuration update fails, restore the old
* persistent configuration if we've changed that.
*/
if ((status != DLADM_STATUS_OK) && (flags & DLADM_OPT_PERSIST)) {
if (dladm_read_conf(linkid, &conf) == DLADM_STATUS_OK) {
u64 = orig_nports;
if ((dladm_set_conf_field(conf, FNPORTS,
DLADM_TYPE_UINT64, &u64) == DLADM_STATUS_OK) &&
(dladm_set_conf_field(conf, FPORTS, DLADM_TYPE_STR,
orig_portstr) == DLADM_STATUS_OK)) {
(void) dladm_write_conf(conf);
}
(void) dladm_destroy_conf(conf);
}
}
free(orig_portstr);
return (status);
}
/*
* Send a modify command to the link aggregation driver.
*/
static dladm_status_t
i_dladm_aggr_modify_sys(datalink_id_t linkid, uint32_t mask,
dladm_aggr_modify_attr_t *attr)
{
laioc_modify_t ioc;
ioc.lu_linkid = linkid;
ioc.lu_modify_mask = 0;
if (mask & DLADM_AGGR_MODIFY_POLICY)
ioc.lu_modify_mask |= LAIOC_MODIFY_POLICY;
if (mask & DLADM_AGGR_MODIFY_MAC)
ioc.lu_modify_mask |= LAIOC_MODIFY_MAC;
if (mask & DLADM_AGGR_MODIFY_LACP_MODE)
ioc.lu_modify_mask |= LAIOC_MODIFY_LACP_MODE;
if (mask & DLADM_AGGR_MODIFY_LACP_TIMER)
ioc.lu_modify_mask |= LAIOC_MODIFY_LACP_TIMER;
ioc.lu_policy = attr->ld_policy;
ioc.lu_mac_fixed = attr->ld_mac_fixed;
bcopy(attr->ld_mac, ioc.lu_mac, ETHERADDRL);
ioc.lu_lacp_mode = attr->ld_lacp_mode;
ioc.lu_lacp_timer = attr->ld_lacp_timer;
if (i_dladm_aggr_strioctl(LAIOC_MODIFY, &ioc, sizeof (ioc)) < 0) {
if (errno == EINVAL)
return (DLADM_STATUS_MACADDRINVAL);
else
return (dladm_errno2status(errno));
} else {
return (DLADM_STATUS_OK);
}
}
/*
* Send a create command to the link aggregation driver.
*/
static dladm_status_t
i_dladm_aggr_create_sys(datalink_id_t linkid, uint16_t key, uint32_t nports,
dladm_aggr_port_attr_db_t *ports, uint32_t policy,
boolean_t mac_addr_fixed, const uchar_t *mac_addr,
aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer, boolean_t force)
{
int i, rc, len;
laioc_create_t *iocp = NULL;
laioc_port_t *ioc_ports;
dladm_status_t status = DLADM_STATUS_OK;
len = sizeof (*iocp) + nports * sizeof (laioc_port_t);
iocp = malloc(len);
if (iocp == NULL)
return (DLADM_STATUS_NOMEM);
iocp->lc_key = key;
iocp->lc_linkid = linkid;
iocp->lc_nports = nports;
iocp->lc_policy = policy;
iocp->lc_lacp_mode = lacp_mode;
iocp->lc_lacp_timer = lacp_timer;
ioc_ports = (laioc_port_t *)(iocp + 1);
iocp->lc_force = force;
for (i = 0; i < nports; i++)
ioc_ports[i].lp_linkid = ports[i].lp_linkid;
if (mac_addr_fixed && !VALID_PORT_MAC(mac_addr)) {
status = DLADM_STATUS_MACADDRINVAL;
goto done;
}
bcopy(mac_addr, iocp->lc_mac, ETHERADDRL);
iocp->lc_mac_fixed = mac_addr_fixed;
rc = i_dladm_aggr_strioctl(LAIOC_CREATE, iocp, len);
if (rc < 0)
status = dladm_errno2status(errno);
done:
free(iocp);
return (status);
}
/*
* Invoked to bring up a link aggregation group.
*/
static int
i_dladm_aggr_up(datalink_id_t linkid, void *arg)
{
dladm_status_t *statusp = (dladm_status_t *)arg;
dladm_aggr_grp_attr_t attr;
dladm_aggr_port_attr_db_t *ports = NULL;
uint16_t key = 0;
int i, j;
dladm_status_t status;
status = dladm_aggr_info(linkid, &attr, DLADM_OPT_PERSIST);
if (status != DLADM_STATUS_OK) {
*statusp = status;
return (DLADM_WALK_CONTINUE);
}
if (attr.lg_key <= AGGR_MAX_KEY)
key = attr.lg_key;
ports = malloc(attr.lg_nports * sizeof (dladm_aggr_port_attr_db_t));
if (ports == NULL) {
status = DLADM_STATUS_NOMEM;
goto done;
}
/*
* Validate (and purge) each physical link associated with this
* aggregation, if the specific hardware has been removed during
* the system shutdown.
*/
for (i = 0, j = 0; i < attr.lg_nports; i++) {
datalink_id_t portid = attr.lg_ports[i].lp_linkid;
uint32_t flags;
dladm_status_t s;
s = dladm_datalink_id2info(portid, &flags, NULL, NULL, NULL, 0);
if (s != DLADM_STATUS_OK || !(flags & DLADM_OPT_ACTIVE))
continue;
ports[j++].lp_linkid = portid;
}
if (j == 0) {
/*
* All of the physical links are removed.
*/
status = DLADM_STATUS_BADARG;
goto done;
}
/*
* Create active aggregation.
*/
if ((status = i_dladm_aggr_create_sys(linkid,
key, j, ports, attr.lg_policy, attr.lg_mac_fixed,
(const uchar_t *)attr.lg_mac, attr.lg_lacp_mode,
attr.lg_lacp_timer, attr.lg_force)) != DLADM_STATUS_OK) {
goto done;
}
if ((status = dladm_up_datalink_id(linkid)) != DLADM_STATUS_OK) {
laioc_delete_t ioc;
ioc.ld_linkid = linkid;
(void) i_dladm_aggr_strioctl(LAIOC_DELETE, &ioc, sizeof (ioc));
goto done;
}
/*
* Reset the active linkprop of this specific link.
*/
(void) dladm_init_linkprop(linkid);
done:
free(attr.lg_ports);
free(ports);
*statusp = status;
return (DLADM_WALK_CONTINUE);
}
/*
* Bring up one aggregation, or all persistent aggregations. In the latter
* case, the walk may terminate early if bringup of an aggregation fails.
*/
dladm_status_t
dladm_aggr_up(datalink_id_t linkid)
{
dladm_status_t status;
if (linkid == DATALINK_ALL_LINKID) {
(void) dladm_walk_datalink_id(i_dladm_aggr_up, &status,
DATALINK_CLASS_AGGR, DATALINK_ANY_MEDIATYPE,
DLADM_OPT_PERSIST);
return (DLADM_STATUS_OK);
} else {
(void) i_dladm_aggr_up(linkid, &status);
return (status);
}
}
/*
* Given a policy string, return a policy mask. Returns B_TRUE on
* success, or B_FALSE if an error occurred during parsing.
*/
boolean_t
dladm_aggr_str2policy(const char *str, uint32_t *policy)
{
int i;
policy_t *pol;
char *token = NULL;
char *lasts;
*policy = 0;
while ((token = strtok_r((token == NULL) ? (char *)str : NULL, ",",
&lasts)) != NULL) {
for (i = 0; i < NPOLICIES; i++) {
pol = &policies[i];
if (strcasecmp(token, pol->pol_name) == 0) {
*policy |= pol->policy;
break;
}
}
if (i == NPOLICIES)
return (B_FALSE);
}
return (B_TRUE);
}
/*
* Given a policy mask, returns a printable string, or NULL if the
* policy mask is invalid. It is the responsibility of the caller to
* free the returned string after use.
*/
char *
dladm_aggr_policy2str(uint32_t policy, char *str)
{
int i, npolicies = 0;
policy_t *pol;
if (str == NULL)
return (NULL);
str[0] = '\0';
for (i = 0; i < NPOLICIES; i++) {
pol = &policies[i];
if ((policy & pol->policy) != 0) {
npolicies++;
if (npolicies > 1)
(void) strlcat(str, ",", DLADM_STRSIZE);
(void) strlcat(str, pol->pol_name, DLADM_STRSIZE);
}
}
return (str);
}
/*
* Given a MAC address string, return the MAC address in the mac_addr
* array. If the MAC address was not explicitly specified, i.e. is
* equal to 'auto', zero out mac-addr and set mac_fixed to B_TRUE.
* Return B_FALSE if a syntax error was encountered, B_FALSE otherwise.
*/
boolean_t
dladm_aggr_str2macaddr(const char *str, boolean_t *mac_fixed, uchar_t *mac_addr)
{
uchar_t *conv_str;
int mac_len;
*mac_fixed = (strcmp(str, "auto") != 0);
if (!*mac_fixed) {
bzero(mac_addr, ETHERADDRL);
return (B_TRUE);
}
conv_str = _link_aton(str, &mac_len);
if (conv_str == NULL)
return (B_FALSE);
if (mac_len != ETHERADDRL) {
free(conv_str);
return (B_FALSE);
}
if ((bcmp(zero_mac, conv_str, ETHERADDRL) == 0) ||
(conv_str[0] & 0x01)) {
free(conv_str);
return (B_FALSE);
}
bcopy(conv_str, mac_addr, ETHERADDRL);
free(conv_str);
return (B_TRUE);
}
/*
* Returns a string containing a printable representation of a MAC address.
*/
const char *
dladm_aggr_macaddr2str(const unsigned char *mac, char *buf)
{
static char unknown_mac[] = {0, 0, 0, 0, 0, 0};
if (buf == NULL)
return (NULL);
if (bcmp(unknown_mac, mac, ETHERADDRL) == 0)
(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
else
return (_link_ntoa(mac, buf, ETHERADDRL, IFT_OTHER));
return (buf);
}
/*
* Given a LACP mode string, find the corresponding LACP mode number. Returns
* B_TRUE if a match was found, B_FALSE otherwise.
*/
boolean_t
dladm_aggr_str2lacpmode(const char *str, aggr_lacp_mode_t *lacp_mode)
{
int i;
dladm_aggr_lacpmode_t *mode;
for (i = 0; i < NLACP_MODES; i++) {
mode = &lacp_modes[i];
if (strncasecmp(str, mode->mode_str,
strlen(mode->mode_str)) == 0) {
*lacp_mode = mode->mode_id;
return (B_TRUE);
}
}
return (B_FALSE);
}
/*
* Given a LACP mode number, returns a printable string, or NULL if the
* LACP mode number is invalid.
*/
const char *
dladm_aggr_lacpmode2str(aggr_lacp_mode_t mode_id, char *buf)
{
int i;
dladm_aggr_lacpmode_t *mode;
if (buf == NULL)
return (NULL);
for (i = 0; i < NLACP_MODES; i++) {
mode = &lacp_modes[i];
if (mode->mode_id == mode_id) {
(void) snprintf(buf, DLADM_STRSIZE, "%s",
mode->mode_str);
return (buf);
}
}
(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
return (buf);
}
/*
* Given a LACP timer string, find the corresponding LACP timer number. Returns
* B_TRUE if a match was found, B_FALSE otherwise.
*/
boolean_t
dladm_aggr_str2lacptimer(const char *str, aggr_lacp_timer_t *lacp_timer)
{
int i;
dladm_aggr_lacptimer_t *timer;
for (i = 0; i < NLACP_TIMERS; i++) {
timer = &lacp_timers[i];
if (strncasecmp(str, timer->lt_str,
strlen(timer->lt_str)) == 0) {
*lacp_timer = timer->lt_id;
return (B_TRUE);
}
}
return (B_FALSE);
}
/*
* Given a LACP timer, returns a printable string, or NULL if the
* LACP timer number is invalid.
*/
const char *
dladm_aggr_lacptimer2str(aggr_lacp_timer_t timer_id, char *buf)
{
int i;
dladm_aggr_lacptimer_t *timer;
if (buf == NULL)
return (NULL);
for (i = 0; i < NLACP_TIMERS; i++) {
timer = &lacp_timers[i];
if (timer->lt_id == timer_id) {
(void) snprintf(buf, DLADM_STRSIZE, "%s",
timer->lt_str);
return (buf);
}
}
(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
return (buf);
}
const char *
dladm_aggr_portstate2str(aggr_port_state_t state_id, char *buf)
{
int i;
dladm_aggr_port_state_t *state;
if (buf == NULL)
return (NULL);
for (i = 0; i < NPORT_STATES; i++) {
state = &port_states[i];
if (state->state_id == state_id) {
(void) snprintf(buf, DLADM_STRSIZE, "%s",
state->state_str);
return (buf);
}
}
(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
return (buf);
}
static dladm_status_t
dladm_aggr_persist_aggr_conf(const char *link, datalink_id_t linkid,
uint16_t key, uint32_t nports, dladm_aggr_port_attr_db_t *ports,
uint32_t policy, boolean_t mac_addr_fixed, const uchar_t *mac_addr,
aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer,
boolean_t force)
{
dladm_conf_t conf = DLADM_INVALID_CONF;
char *portstr = NULL;
char macstr[ETHERADDRL * 3];
dladm_status_t status;
int i, size;
uint64_t u64;
if ((status = dladm_create_conf(link, linkid, DATALINK_CLASS_AGGR,
DL_ETHER, &conf)) != DLADM_STATUS_OK) {
return (status);
}
u64 = key;
status = dladm_set_conf_field(conf, FKEY, DLADM_TYPE_UINT64, &u64);
if (status != DLADM_STATUS_OK)
goto done;
u64 = nports;
status = dladm_set_conf_field(conf, FNPORTS, DLADM_TYPE_UINT64, &u64);
if (status != DLADM_STATUS_OK)
goto done;
size = nports * (LINKID_STR_WIDTH + 1) + 1;
if ((portstr = calloc(1, size)) == NULL) {
status = DLADM_STATUS_NOMEM;
goto done;
}
for (i = 0; i < nports; i++)
WRITE_PORT(portstr, ports[i].lp_linkid, size);
status = dladm_set_conf_field(conf, FPORTS, DLADM_TYPE_STR, portstr);
free(portstr);
if (status != DLADM_STATUS_OK)
goto done;
u64 = policy;
status = dladm_set_conf_field(conf, FPOLICY, DLADM_TYPE_UINT64, &u64);
if (status != DLADM_STATUS_OK)
goto done;
status = dladm_set_conf_field(conf, FFIXMACADDR, DLADM_TYPE_BOOLEAN,
&mac_addr_fixed);
if (status != DLADM_STATUS_OK)
goto done;
if (mac_addr_fixed) {
if (!VALID_PORT_MAC(mac_addr)) {
status = DLADM_STATUS_MACADDRINVAL;
goto done;
}
(void) dladm_aggr_macaddr2str(mac_addr, macstr);
status = dladm_set_conf_field(conf, FMACADDR, DLADM_TYPE_STR,
macstr);
if (status != DLADM_STATUS_OK)
goto done;
}
status = dladm_set_conf_field(conf, FFORCE, DLADM_TYPE_BOOLEAN, &force);
if (status != DLADM_STATUS_OK)
goto done;
u64 = lacp_mode;
status = dladm_set_conf_field(conf, FLACPMODE, DLADM_TYPE_UINT64, &u64);
if (status != DLADM_STATUS_OK)
goto done;
u64 = lacp_timer;
status = dladm_set_conf_field(conf, FLACPTIMER, DLADM_TYPE_UINT64,
&u64);
if (status != DLADM_STATUS_OK)
goto done;
/*
* Commit the link aggregation configuration.
*/
status = dladm_write_conf(conf);
done:
dladm_destroy_conf(conf);
return (status);
}
/*
* Create a new link aggregation group. Update the configuration
* file and bring it up.
*/
dladm_status_t
dladm_aggr_create(const char *name, uint16_t key, uint32_t nports,
dladm_aggr_port_attr_db_t *ports, uint32_t policy, boolean_t mac_addr_fixed,
const uchar_t *mac_addr, aggr_lacp_mode_t lacp_mode,
aggr_lacp_timer_t lacp_timer, uint32_t flags)
{
datalink_id_t linkid = DATALINK_INVALID_LINKID;
uint32_t media;
uint32_t i;
datalink_class_t class;
dladm_status_t status;
boolean_t force = (flags & DLADM_OPT_FORCE) ? B_TRUE : B_FALSE;
if (key != 0 && key > AGGR_MAX_KEY)
return (DLADM_STATUS_KEYINVAL);
if (nports == 0)
return (DLADM_STATUS_BADARG);
for (i = 0; i < nports; i++) {
if ((dladm_datalink_id2info(ports[i].lp_linkid, NULL,
&class, &media, NULL, 0) != DLADM_STATUS_OK) ||
(class != DATALINK_CLASS_PHYS) && (media != DL_ETHER)) {
return (DLADM_STATUS_BADARG);
}
}
flags &= (DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST);
if ((status = dladm_create_datalink_id(name, DATALINK_CLASS_AGGR,
DL_ETHER, flags, &linkid)) != DLADM_STATUS_OK) {
goto fail;
}
if ((flags & DLADM_OPT_PERSIST) &&
(status = dladm_aggr_persist_aggr_conf(name, linkid, key, nports,
ports, policy, mac_addr_fixed, mac_addr, lacp_mode, lacp_timer,
force)) != DLADM_STATUS_OK) {
goto fail;
}
if (!(flags & DLADM_OPT_ACTIVE))
return (DLADM_STATUS_OK);
status = i_dladm_aggr_create_sys(linkid, key, nports, ports, policy,
mac_addr_fixed, mac_addr, lacp_mode, lacp_timer, force);
if (status != DLADM_STATUS_OK) {
if (flags & DLADM_OPT_PERSIST)
(void) dladm_remove_conf(linkid);
goto fail;
}
return (DLADM_STATUS_OK);
fail:
if (linkid != DATALINK_INVALID_LINKID)
(void) dladm_destroy_datalink_id(linkid, flags);
return (status);
}
static dladm_status_t
i_dladm_aggr_get_aggr_attr(dladm_conf_t conf, uint32_t mask,
dladm_aggr_modify_attr_t *attrp)
{
dladm_status_t status = DLADM_STATUS_OK;
char macstr[ETHERADDRL * 3];
uint64_t u64;
if (mask & DLADM_AGGR_MODIFY_POLICY) {
status = dladm_get_conf_field(conf, FPOLICY, &u64,
sizeof (u64));
if (status != DLADM_STATUS_OK)
return (status);
attrp->ld_policy = (uint32_t)u64;
}
if (mask & DLADM_AGGR_MODIFY_MAC) {
status = dladm_get_conf_field(conf, FFIXMACADDR,
&attrp->ld_mac_fixed, sizeof (boolean_t));
if (status != DLADM_STATUS_OK)
return (status);
if (attrp->ld_mac_fixed) {
boolean_t fixed;
status = dladm_get_conf_field(conf, FMACADDR,
macstr, sizeof (macstr));
if (status != DLADM_STATUS_OK)
return (status);
if (!dladm_aggr_str2macaddr(macstr, &fixed,
attrp->ld_mac)) {
return (DLADM_STATUS_REPOSITORYINVAL);
}
}
}
if (mask & DLADM_AGGR_MODIFY_LACP_MODE) {
status = dladm_get_conf_field(conf, FLACPMODE, &u64,
sizeof (u64));
if (status != DLADM_STATUS_OK)
return (status);
attrp->ld_lacp_mode = (aggr_lacp_mode_t)u64;
}
if (mask & DLADM_AGGR_MODIFY_LACP_TIMER) {
status = dladm_get_conf_field(conf, FLACPTIMER, &u64,
sizeof (u64));
if (status != DLADM_STATUS_OK)
return (status);
attrp->ld_lacp_timer = (aggr_lacp_timer_t)u64;
}
return (status);
}
static dladm_status_t
i_dladm_aggr_set_aggr_attr(dladm_conf_t conf, uint32_t mask,
dladm_aggr_modify_attr_t *attrp)
{
dladm_status_t status = DLADM_STATUS_OK;
char macstr[ETHERADDRL * 3];
uint64_t u64;
if (mask & DLADM_AGGR_MODIFY_POLICY) {
u64 = attrp->ld_policy;
status = dladm_set_conf_field(conf, FPOLICY, DLADM_TYPE_UINT64,
&u64);
if (status != DLADM_STATUS_OK)
return (status);
}
if (mask & DLADM_AGGR_MODIFY_MAC) {
status = dladm_set_conf_field(conf, FFIXMACADDR,
DLADM_TYPE_BOOLEAN, &attrp->ld_mac_fixed);
if (status != DLADM_STATUS_OK)
return (status);
if (attrp->ld_mac_fixed) {
(void) dladm_aggr_macaddr2str(attrp->ld_mac, macstr);
status = dladm_set_conf_field(conf, FMACADDR,
DLADM_TYPE_STR, macstr);
if (status != DLADM_STATUS_OK)
return (status);
}
}
if (mask & DLADM_AGGR_MODIFY_LACP_MODE) {
u64 = attrp->ld_lacp_mode;
status = dladm_set_conf_field(conf, FLACPMODE,
DLADM_TYPE_UINT64, &u64);
if (status != DLADM_STATUS_OK)
return (status);
}
if (mask & DLADM_AGGR_MODIFY_LACP_TIMER) {
u64 = attrp->ld_lacp_timer;
status = dladm_set_conf_field(conf, FLACPTIMER,
DLADM_TYPE_UINT64, &u64);
if (status != DLADM_STATUS_OK)
return (status);
}
return (status);
}
/*
* Modify the parameters of an existing link aggregation group. Update
* the configuration file and pass the changes to the kernel.
*/
dladm_status_t
dladm_aggr_modify(datalink_id_t linkid, uint32_t modify_mask, uint32_t policy,
boolean_t mac_fixed, const uchar_t *mac_addr, aggr_lacp_mode_t lacp_mode,
aggr_lacp_timer_t lacp_timer, uint32_t flags)
{
dladm_aggr_modify_attr_t new_attr, old_attr;
dladm_conf_t conf;
dladm_status_t status;
new_attr.ld_policy = policy;
new_attr.ld_mac_fixed = mac_fixed;
new_attr.ld_lacp_mode = lacp_mode;
new_attr.ld_lacp_timer = lacp_timer;
bcopy(mac_addr, new_attr.ld_mac, ETHERADDRL);
if (flags & DLADM_OPT_PERSIST) {
status = dladm_read_conf(linkid, &conf);
if (status != DLADM_STATUS_OK)
return (status);
if ((status = i_dladm_aggr_get_aggr_attr(conf, modify_mask,
&old_attr)) != DLADM_STATUS_OK) {
goto done;
}
if ((status = i_dladm_aggr_set_aggr_attr(conf, modify_mask,
&new_attr)) != DLADM_STATUS_OK) {
goto done;
}
status = dladm_write_conf(conf);
done:
dladm_destroy_conf(conf);
if (status != DLADM_STATUS_OK)
return (status);
}
if (!(flags & DLADM_OPT_ACTIVE))
return (DLADM_STATUS_OK);
status = i_dladm_aggr_modify_sys(linkid, modify_mask, &new_attr);
if ((status != DLADM_STATUS_OK) && (flags & DLADM_OPT_PERSIST)) {
if (dladm_read_conf(linkid, &conf) == DLADM_STATUS_OK) {
if (i_dladm_aggr_set_aggr_attr(conf, modify_mask,
&old_attr) == DLADM_STATUS_OK) {
(void) dladm_write_conf(conf);
}
dladm_destroy_conf(conf);
}
}
return (status);
}
typedef struct aggr_held_arg_s {
datalink_id_t aggrid;
boolean_t isheld;
} aggr_held_arg_t;
static int
i_dladm_aggr_is_held(datalink_id_t linkid, void *arg)
{
aggr_held_arg_t *aggr_held_arg = arg;
dladm_vlan_attr_t dva;
if (dladm_vlan_info(linkid, &dva, DLADM_OPT_PERSIST) != DLADM_STATUS_OK)
return (DLADM_WALK_CONTINUE);
if (dva.dv_linkid == aggr_held_arg->aggrid) {
/*
* This VLAN is created over the given aggregation.
*/
aggr_held_arg->isheld = B_TRUE;
return (DLADM_WALK_TERMINATE);
}
return (DLADM_WALK_CONTINUE);
}
/*
* Delete a previously created link aggregation group. Either the name "aggr"
* or the "key" is specified.
*/
dladm_status_t
dladm_aggr_delete(datalink_id_t linkid, uint32_t flags)
{
laioc_delete_t ioc;
datalink_class_t class;
dladm_status_t status;
if ((dladm_datalink_id2info(linkid, NULL, &class, NULL, NULL, 0) !=
DLADM_STATUS_OK) || (class != DATALINK_CLASS_AGGR)) {
return (DLADM_STATUS_BADARG);
}
if (flags & DLADM_OPT_ACTIVE) {
ioc.ld_linkid = linkid;
if ((i_dladm_aggr_strioctl(LAIOC_DELETE, &ioc,
sizeof (ioc)) < 0) &&
((errno != ENOENT) || !(flags & DLADM_OPT_PERSIST))) {
status = dladm_errno2status(errno);
return (status);
}
/*
* Delete ACTIVE linkprop first.
*/
(void) dladm_set_linkprop(linkid, NULL, NULL, 0,
DLADM_OPT_ACTIVE);
(void) dladm_destroy_datalink_id(linkid, DLADM_OPT_ACTIVE);
}
/*
* If we reach here, it means that the active aggregation has already
* been deleted, and there is no active VLANs holding this aggregation.
* Now we see whether there is any persistent VLANs holding this
* aggregation. If so, we fail the operation.
*/
if (flags & DLADM_OPT_PERSIST) {
aggr_held_arg_t arg;
arg.aggrid = linkid;
arg.isheld = B_FALSE;
(void) dladm_walk_datalink_id(i_dladm_aggr_is_held,
&arg, DATALINK_CLASS_VLAN, DATALINK_ANY_MEDIATYPE,
DLADM_OPT_PERSIST);
if (arg.isheld)
return (DLADM_STATUS_LINKBUSY);
(void) dladm_destroy_datalink_id(linkid, DLADM_OPT_PERSIST);
(void) dladm_remove_conf(linkid);
}
return (DLADM_STATUS_OK);
}
/*
* Add one or more ports to an existing link aggregation.
*/
dladm_status_t
dladm_aggr_add(datalink_id_t linkid, uint32_t nports,
dladm_aggr_port_attr_db_t *ports, uint32_t flags)
{
return (i_dladm_aggr_add_rmv(linkid, nports, ports, flags, LAIOC_ADD));
}
/*
* Remove one or more ports from an existing link aggregation.
*/
dladm_status_t
dladm_aggr_remove(datalink_id_t linkid, uint32_t nports,
dladm_aggr_port_attr_db_t *ports, uint32_t flags)
{
return (i_dladm_aggr_add_rmv(linkid, nports, ports, flags,
LAIOC_REMOVE));
}
typedef struct i_walk_key_state_s {
uint16_t key;
datalink_id_t linkid;
boolean_t found;
} i_walk_key_state_t;
static int
i_dladm_walk_key2linkid(datalink_id_t linkid, void *arg)
{
dladm_conf_t conf;
uint16_t key;
dladm_status_t status;
i_walk_key_state_t *statep = (i_walk_key_state_t *)arg;
uint64_t u64;
if (dladm_read_conf(linkid, &conf) != 0)
return (DLADM_WALK_CONTINUE);
status = dladm_get_conf_field(conf, FKEY, &u64, sizeof (u64));
key = (uint16_t)u64;
dladm_destroy_conf(conf);
if ((status == DLADM_STATUS_OK) && (key == statep->key)) {
statep->found = B_TRUE;
statep->linkid = linkid;
return (DLADM_WALK_TERMINATE);
}
return (DLADM_WALK_CONTINUE);
}
dladm_status_t
dladm_key2linkid(uint16_t key, datalink_id_t *linkidp, uint32_t flags)
{
i_walk_key_state_t state;
if (key > AGGR_MAX_KEY)
return (DLADM_STATUS_NOTFOUND);
state.found = B_FALSE;
state.key = key;
(void) dladm_walk_datalink_id(i_dladm_walk_key2linkid, &state,
DATALINK_CLASS_AGGR, DATALINK_ANY_MEDIATYPE, flags);
if (state.found == B_TRUE) {
*linkidp = state.linkid;
return (DLADM_STATUS_OK);
} else {
return (DLADM_STATUS_NOTFOUND);
}
}