libdlaggr.c revision 843e19887f64dde75055cf8842fc4db2171eff45
/*
* 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
* 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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stropts.h>
#include <stdlib.h>
#include <errno.h>
#include <strings.h>
#include <libintl.h>
#include <net/if_types.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.
*
* Link aggregation configuration information is saved in a text
* file of the following format:
*
* <db-file> ::= <groups>*
* <group> ::= <key> <sep> <policy> <sep> <nports> <sep> <ports> <sep>
* <mac> <sep> <lacp-mode> <sep> <lacp-timer>
* <sep> ::= ' ' | '\t'
* <key> ::= <number>
* <nports> ::= <number>
* <ports> ::= <port> <m-port>*
* <m-port> ::= ',' <port>
* <port> ::= <devname>
* <devname> ::= <string>
* <port-num> ::= <number>
* <policy> ::= <pol-level> <m-pol>*
* <m-pol> ::= ',' <pol-level>
* <pol-level> ::= 'L2' | 'L3' | 'L4'
* <mac> ::= 'auto' | <mac-addr>
* <mac-addr> ::= <hex> ':' <hex> ':' <hex> ':' <hex> ':' <hex> ':' <hex>
* <lacp-mode> ::= 'off' | 'active' | 'passive'
* <lacp-timer> ::= 'short' | 'long'
*/
#define DLADM_AGGR_DB "/etc/dladm/aggregation.conf"
#define DLADM_AGGR_DB_TMP "/etc/dladm/aggregation.conf.new"
#define DLADM_AGGR_DB_LOCK "/tmp/aggregation.conf.lock"
/*
* The largest configurable aggregation key. Because by default the key is
* used as the DLPI device PPA and default VLAN PPA's are calculated as
* ((1000 * vid) + PPA), the largest key can't be > 999.
*/
#define DLADM_AGGR_MAX_KEY 999
/* Limits on buffer size for LAIOC_INFO request */
#define MAXPATHLEN 1024
/* configuration database entry */
typedef struct dladm_aggr_grp_attr_db {
typedef struct dladm_aggr_up {
int lu_fd;
typedef struct dladm_aggr_down {
typedef struct dladm_aggr_modify_attr {
typedef struct policy_s {
char *pol_name;
} policy_t;
{"L2", AGGR_POLICY_L2},
{"L3", AGGR_POLICY_L3},
{"L4", AGGR_POLICY_L4}};
typedef struct dladm_aggr_lacpmode_s {
char *mode_str;
static dladm_aggr_lacpmode_t lacp_modes[] = {
{"off", AGGR_LACP_OFF},
{"active", AGGR_LACP_ACTIVE},
{"passive", AGGR_LACP_PASSIVE}};
typedef struct dladm_aggr_lacptimer_s {
char *lt_str;
static dladm_aggr_lacptimer_t lacp_timers[] = {
{"short", AGGR_LACP_TIMER_SHORT},
{"long", AGGR_LACP_TIMER_LONG}};
typedef struct dladm_aggr_port_state {
char *state_str;
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))
typedef struct delete_db_state {
typedef struct modify_db_state {
typedef struct add_db_state {
/*
* Open and lock the aggregation configuration file lock. The lock is
* acquired as a reader (F_RDLCK) or writer (F_WRLCK).
*/
static int
i_dladm_aggr_lock_db(short type)
{
int lock_fd;
int errno_save;
DLADM_AGGR_DB_PERMS)) < 0)
return (-1);
errno_save = errno;
(void) unlink(DLADM_AGGR_DB_LOCK);
errno = errno_save;
return (-1);
}
return (lock_fd);
}
/*
* Unlock and close the specified file.
*/
static void
i_dladm_aggr_unlock_db(int fd)
{
if (fd < 0)
return;
(void) unlink(DLADM_AGGR_DB_LOCK);
}
/*
* Walk through the groups defined on the system and for each group <grp>,
* invoke <fn>(<arg>, <grp>);
* Terminate the walk if at any time <fn> returns non-NULL value
*/
int
{
char *where;
return (-1);
return (-1);
}
if (rc != 0) {
/*
* The LAIOC_INFO call failed due to a short
* buffer. Reallocate the buffer and try again.
*/
bufsize *= 2;
if (bufsize <= MAX_INFO_SIZE) {
goto tryagain;
}
}
}
goto bail;
}
/*
* Go through each group returned by the aggregation driver.
*/
for (i = 0; i < ioc->li_ngroups; i++) {
/* LINTED E_BAD_PTR_CAST_ALIGN */
sizeof (dladm_aggr_port_attr_t));
goto bail;
}
/*
* Go through each port that is part of the group.
*/
/* LINTED E_BAD_PTR_CAST_ALIGN */
MAXNAMELEN + 1);
}
if (rc != 0)
goto bail;
}
bail:
return (rc);
}
/*
* Parse one line of the link aggregation DB, and return the corresponding
* group. Memory for the ports associated with the aggregation may be
* allocated. It is the responsibility of the caller to free the lt_ports
* aggregation group attribute.
*
* Returns -1 on parsing failure, or 0 on success.
*/
static int
{
char *token;
int i;
int value;
/* key */
goto failed;
errno = 0;
goto failed;
/* policy */
goto failed;
/* number of ports */
return (-1);
errno = 0;
goto failed;
/* ports */
sizeof (dladm_aggr_port_attr_db_t))) == NULL)
goto failed;
/* port */
goto failed;
/*
* device name: In a previous version of this file, a port
* number could be specified using <devname>/<portnum>.
* This syntax is unecessary and obsolete.
*/
goto failed;
MAXNAMELEN) >= MAXNAMELEN)
goto failed;
}
/* unicast MAC address */
goto failed;
/* LACP mode */
/* LACP timer */
return (0);
return (-1);
}
/*
* Walk through the groups defined in the DB and for each group <grp>,
* invoke <fn>(<arg>, <grp>);
*/
static dladm_status_t
{
char line[MAXLINELEN];
char *db_file;
char db_file_buf[MAXPATHLEN];
int lock_fd;
} else {
}
return (status);
}
/* skip comments */
if (BLANK_LINE(line))
continue;
goto done;
}
goto done;
}
done:
return (status);
}
/*
* Send an add or remove command to the link aggregation driver.
*/
static dladm_status_t
{
goto done;
}
MAXNAMELEN) >= MAXNAMELEN) {
goto done;
}
}
goto done;
}
if (rc < 0) {
else
}
done:
return (status);
}
/*
* Send a modify command to the link aggregation driver.
*/
static dladm_status_t
{
ioc.lu_modify_mask = 0;
if (mask & DLADM_AGGR_MODIFY_POLICY)
if (mask & DLADM_AGGR_MODIFY_MAC)
if (mask & DLADM_AGGR_MODIFY_LACP_MODE)
if (mask & DLADM_AGGR_MODIFY_LACP_TIMER)
return (dladm_errno2status(errno));
if (rc < 0) {
else
}
return (status);
}
/*
* Send a create command to the link aggregation driver.
*/
static dladm_status_t
{
return (DLADM_STATUS_NOMEM);
MAXNAMELEN) >= MAXNAMELEN) {
return (DLADM_STATUS_BADARG);
}
}
if (attr->lt_mac_fixed &&
return (DLADM_STATUS_MACADDRINVAL);
}
if (rc < 0)
return (status);
}
/*
* Invoked to bring up a link aggregation group.
*/
static dladm_status_t
{
return (DLADM_STATUS_OK);
return (status);
return (DLADM_STATUS_OK);
}
/*
* Bring up a link aggregation group or all of them if the key is zero.
* If key is 0, walk may terminate early if any of the links fail
*/
{
return (dladm_errno2status(errno));
if (status != DLADM_STATUS_OK) {
return (status);
}
/*
* only return error if user specified key and key was
* not found
*/
return (DLADM_STATUS_NOTFOUND);
return (DLADM_STATUS_OK);
}
/*
* Send a delete command to the link aggregation driver.
*/
static int
{
}
/*
* Invoked to bring down a link aggregation group.
*/
static int
{
int fd, errno_save;
return (0);
return (-1);
errno_save = errno;
errno = errno_save;
return (-1);
}
return (0);
}
/*
* Bring down a link aggregation group or all of them if the key is zero.
* If key is 0, walk may terminate early if any of the links fail
*/
{
return (dladm_errno2status(errno));
/*
* only return error if user specified key and key was
* not found
*/
return (DLADM_STATUS_NOTFOUND);
return (DLADM_STATUS_OK);
}
/*
* For each group <grp> found in the DB, invokes <fn>(<grp>, <arg>).
*
* The following values can be returned by <fn>():
*
* -1: an error occured. This will cause the walk to be terminated,
* and the original DB file to be preserved.
*
* 0: success and write. The walker will write the contents of
* the attribute passed as argument to <fn>(), and continue walking
* the entries found in the DB.
*
* 1: skip. The walker should not write the contents of the current
* group attributes to the new DB, but should continue walking
* the entries found in the DB.
*/
static dladm_status_t
{
char line[MAXLINELEN];
char *db_file, *tmp_db_file;
char db_file_buf[MAXPATHLEN];
char tmp_db_file_buf[MAXPATHLEN];
} else {
}
return (dladm_errno2status(errno));
return (status);
}
DLADM_AGGR_DB_PERMS)) == -1) {
return (status);
}
(void) unlink(tmp_db_file);
return (status);
}
/* skip comments */
if (BLANK_LINE(line)) {
goto failed;
}
continue;
}
goto failed;
}
switch (fn_rc) {
case -1:
/* failure, stop walking */
goto failed;
case 0:
/*
* Success, write group attributes, which could
* have been modified by fn().
*/
goto failed;
}
break;
case 1:
/* skip current group */
break;
}
}
goto failed;
}
DLADM_AGGR_DB_GROUP) == -1) {
goto failed;
}
}
goto failed;
}
(void) unlink(tmp_db_file);
return (status);
}
return (DLADM_STATUS_OK);
(void) unlink(tmp_db_file);
return (status);
}
/*
* Remove an entry from the DB.
*/
static int
{
return (0);
/* don't save matching group */
return (1);
}
static dladm_status_t
{
if (status != DLADM_STATUS_OK)
return (status);
return (DLADM_STATUS_NOTFOUND);
return (DLADM_STATUS_OK);
}
/*
* Modify the properties of an existing group in the DB.
*/
static int
{
return (0);
}
}
}
}
}
/* save modified group */
return (0);
}
static dladm_status_t
const char *root)
{
return (status);
}
return (DLADM_STATUS_NOTFOUND);
return (DLADM_STATUS_OK);
}
/*
* Add ports to an existing group in the DB.
*/
static int
{
void *ports;
int i, j;
return (0);
/* are any of the ports to be added already members of the group? */
return (-1);
}
}
}
/* add groups specified by attr to grp */
return (-1);
MAXNAMELEN + 1)
return (-1);
}
/* save modified group */
return (0);
}
static dladm_status_t
{
if (status != DLADM_STATUS_OK)
return (status);
return (DLADM_STATUS_NOTFOUND);
return (DLADM_STATUS_OK);
}
/*
* Remove ports from an existing group in the DB.
*/
typedef struct remove_db_state {
static int
{
int i, j, k, nremoved;
return (0);
/* remove the ports specified by attr from the group */
nremoved = 0;
k = 0;
}
if (match)
nremoved++;
else
}
return (-1);
}
/* save modified group */
return (0);
}
static dladm_status_t
{
if (status != DLADM_STATUS_OK)
return (status);
return (DLADM_STATUS_NOTFOUND);
return (DLADM_STATUS_OK);
}
/*
* Given a policy string, return a policy mask. Returns B_TRUE on
* success, or B_FALSE if an error occured during parsing.
*/
{
int i;
char *lasts;
*policy = 0;
for (i = 0; i < NPOLICIES; i++) {
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 *
{
int i, npolicies = 0;
str[0] = '\0';
for (i = 0; i < NPOLICIES; i++) {
npolicies++;
if (npolicies > 1)
}
}
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.
*/
{
int mac_len;
if (!*mac_fixed) {
return (B_TRUE);
}
return (B_FALSE);
if (mac_len != ETHERADDRL) {
return (B_FALSE);
}
(conv_str[0] & 0x01)) {
return (B_FALSE);
}
return (B_TRUE);
}
/*
* Returns a string containing a printable representation of a MAC address.
*/
const char *
{
static char unknown_mac[] = {0, 0, 0, 0, 0, 0};
return (NULL);
return (gettext("<unknown>"));
else
}
/*
* Given a LACP mode string, find the corresponding LACP mode number. Returns
* B_TRUE if a match was found, B_FALSE otherwise.
*/
{
int i;
for (i = 0; i < NLACP_MODES; i++) {
mode = &lacp_modes[i];
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 *
{
int i;
for (i = 0; i < NLACP_MODES; i++) {
mode = &lacp_modes[i];
return (buf);
}
}
return (buf);
}
/*
* Given a LACP timer string, find the corresponding LACP timer number. Returns
* B_TRUE if a match was found, B_FALSE otherwise.
*/
{
int i;
for (i = 0; i < NLACP_TIMERS; i++) {
timer = &lacp_timers[i];
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 *
{
int i;
for (i = 0; i < NLACP_TIMERS; i++) {
timer = &lacp_timers[i];
return (buf);
}
}
return (buf);
}
const char *
{
int i;
for (i = 0; i < NPORT_STATES; i++) {
state = &port_states[i];
return (buf);
}
}
return (buf);
}
/*
* Write the attribute of a group to the specified file. Returns 0 on
* success, -1 on failure.
*/
static int
{
int i;
char buf[DLADM_STRSIZE];
/* key, policy */
/* number of ports, ports */
if (i > 0)
}
/* MAC address */
if (!attr->lt_mac_fixed) {
} else {
}
return (0);
}
static dladm_status_t
{
char line[MAXLINELEN];
int lock_fd;
char *db_file;
char db_file_buf[MAXPATHLEN];
} else {
}
return (dladm_errno2status(errno));
return (status);
}
/* look for existing group with same key */
/* skip comments */
if (BLANK_LINE(line))
continue;
/* ignore corrupted lines */
continue;
/* port number */
errno = 0;
goto done;
}
/* group with key already exists */
goto done;
}
}
/*
* If we get here, we've verified that no existing group with
* the same key already exists. It's now time to add the
* new group to the DB.
*/
goto done;
}
done:
return (status);
}
/*
* Create a new link aggregation group. Update the configuration
* file and bring it up.
*/
{
return (DLADM_STATUS_KEYINVAL);
if (attr.lt_mac_fixed)
else
/* add the link aggregation group to the DB */
if (!tempop) {
if (status != DLADM_STATUS_OK)
return (status);
} else {
return (dladm_errno2status(errno));
return (status);
}
/* bring up the link aggregation group */
/*
* If the operation fails because the aggregation already exists,
* then only update the persistent configuration repository and
* return success.
*/
if (status == DLADM_STATUS_EXIST)
return (status);
}
/*
* Modify the parameters of an existing link aggregation group. Update
* the configuration file and pass the changes to the kernel.
*/
{
if (key == 0)
return (DLADM_STATUS_KEYINVAL);
if (modify_mask & DLADM_AGGR_MODIFY_MAC) {
}
/* update the DB */
return (status);
}
}
return (status);
}
/*
* Delete a previously created link aggregation group.
*/
{
if (key == 0)
return (DLADM_STATUS_KEYINVAL);
if (tempop) {
return (dladm_errno2status(errno));
else
return (DLADM_STATUS_OK);
} else {
/*
* Only continue to delete the configuration repository
* either if we successfully delete the active aggregation
* or if the aggregation is not found.
*/
if (status != DLADM_STATUS_OK &&
status != DLADM_STATUS_NOTFOUND) {
return (status);
}
}
if (tempop)
return (DLADM_STATUS_OK);
}
/*
* Add one or more ports to an existing link aggregation.
*/
{
if (key == 0)
return (DLADM_STATUS_KEYINVAL);
if (!tempop &&
return (status);
}
return (status);
}
/*
* Remove one or more ports from an existing link aggregation.
*/
{
if (key == 0)
return (DLADM_STATUS_KEYINVAL);
if (!tempop &&
DLADM_STATUS_OK)) {
return (status);
}
return (status);
}