/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Link Aggregation Administration Library. * * This library is used by administration tools such as dladm(1M) to * configure link aggregations. */ /* 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_ioctl(int cmd, void *ptr) { int err, fd; if ((fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0) return (-1); err = ioctl(fd, cmd, ptr); (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 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: ioc->li_bufsize = bufsize; if (i_dladm_aggr_ioctl(LAIOC_INFO, ioc) != 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_ioctl(cmd, iocp) < 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_ioctl(LAIOC_MODIFY, &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, 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; if (i_dladm_aggr_ioctl(LAIOC_CREATE, iocp) < 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_ioctl(LAIOC_DELETE, &ioc); goto done; } /* * Reset the active linkprop of this specific link. */ (void) dladm_init_linkprop(linkid, B_FALSE); 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_ioctl(LAIOC_DELETE, &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); } }