/* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #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. * * Link aggregation configuration information is saved in a text * file of the following format: * * ::= * * ::= * * ::= ' ' | '\t' * ::= * ::= * ::= * * ::= ',' * ::= * ::= * ::= * ::= * * ::= ',' * ::= 'L2' | 'L3' | 'L4' * ::= 'auto' | * ::= ':' ':' ':' ':' ':' * ::= 'off' | 'active' | 'passive' * ::= 'short' | 'long' */ #define DLADM_AGGR_DEV "/devices/pseudo/aggr@0:" AGGR_DEVNAME_CTL #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" #define DLADM_AGGR_DB_PERMS S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH #define DLADM_AGGR_DB_OWNER 15 /* "dladm" UID */ #define DLADM_AGGR_DB_GROUP 3 /* "sys" GID */ /* * 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 #define BLANK_LINE(s) ((s[0] == '\0') || (s[0] == '#') || (s[0] == '\n')) /* Limits on buffer size for LAIOC_INFO request */ #define MIN_INFO_SIZE (4*1024) #define MAX_INFO_SIZE (128*1024) #define MAXPATHLEN 1024 static uchar_t zero_mac[] = {0, 0, 0, 0, 0, 0}; /* configuration database entry */ typedef struct dladm_aggr_grp_attr_db { uint32_t lt_key; uint32_t lt_policy; uint32_t lt_nports; dladm_aggr_port_attr_db_t *lt_ports; boolean_t lt_mac_fixed; uchar_t lt_mac[ETHERADDRL]; aggr_lacp_mode_t lt_lacp_mode; aggr_lacp_timer_t lt_lacp_timer; } dladm_aggr_grp_attr_db_t; typedef struct dladm_aggr_up { uint32_t lu_key; boolean_t lu_found; int lu_fd; } dladm_aggr_up_t; typedef struct dladm_aggr_down { uint32_t ld_key; boolean_t ld_found; } dladm_aggr_down_t; 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)) typedef struct delete_db_state { uint32_t ds_key; boolean_t ds_found; } delete_db_state_t; typedef struct modify_db_state { uint32_t us_key; uint32_t us_mask; dladm_aggr_modify_attr_t *us_attr_new; dladm_aggr_modify_attr_t *us_attr_old; boolean_t us_found; } modify_db_state_t; typedef struct add_db_state { dladm_aggr_grp_attr_db_t *as_attr; boolean_t as_found; } add_db_state_t; static int i_dladm_aggr_fput_grp(FILE *, dladm_aggr_grp_attr_db_t *); static int i_dladm_aggr_strioctl(int fd, int cmd, void *ptr, int ilen) { struct strioctl str; str.ic_cmd = cmd; str.ic_timout = 0; str.ic_len = ilen; str.ic_dp = ptr; return (ioctl(fd, I_STR, &str)); } /* * 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; struct flock lock; int errno_save; if ((lock_fd = open(DLADM_AGGR_DB_LOCK, O_RDWR | O_CREAT | O_TRUNC, DLADM_AGGR_DB_PERMS)) < 0) return (-1); lock.l_type = type; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(lock_fd, F_SETLKW, &lock) < 0) { errno_save = errno; (void) close(lock_fd); (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) { struct flock lock; if (fd < 0) return; lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; (void) fcntl(fd, F_SETLKW, &lock); (void) close(fd); (void) unlink(DLADM_AGGR_DB_LOCK); } /* * Walk through the groups defined on the system and for each group , * invoke (, ); * Terminate the walk if at any time returns non-NULL value */ int dladm_aggr_walk(int (*fn)(void *, dladm_aggr_grp_attr_t *), void *arg) { laioc_info_t *ioc; laioc_info_group_t *grp; laioc_info_port_t *port; dladm_aggr_grp_attr_t attr; int rc, i, j, bufsize, fd; char *where; if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) == -1) return (-1); bufsize = MIN_INFO_SIZE; ioc = (laioc_info_t *)calloc(1, bufsize); if (ioc == NULL) { (void) close(fd); errno = ENOMEM; return (-1); } tryagain: rc = i_dladm_aggr_strioctl(fd, 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; } } } goto bail; } /* * Go through each group returned by the aggregation driver. */ where = (char *)(ioc + 1); for (i = 0; i < ioc->li_ngroups; i++) { /* LINTED E_BAD_PTR_CAST_ALIGN */ grp = (laioc_info_group_t *)where; attr.lg_key = grp->lg_key; attr.lg_nports = grp->lg_nports; attr.lg_policy = grp->lg_policy; attr.lg_lacp_mode = grp->lg_lacp_mode; attr.lg_lacp_timer = grp->lg_lacp_timer; bcopy(grp->lg_mac, attr.lg_mac, ETHERADDRL); attr.lg_mac_fixed = grp->lg_mac_fixed; attr.lg_ports = malloc(grp->lg_nports * sizeof (dladm_aggr_port_attr_t)); if (attr.lg_ports == NULL) { errno = ENOMEM; goto bail; } where = (char *)(grp + 1); /* * Go through each port that is part of the group. */ for (j = 0; j < grp->lg_nports; j++) { /* LINTED E_BAD_PTR_CAST_ALIGN */ port = (laioc_info_port_t *)where; bcopy(port->lp_devname, attr.lg_ports[j].lp_devname, MAXNAMELEN + 1); bcopy(port->lp_mac, attr.lg_ports[j].lp_mac, ETHERADDRL); attr.lg_ports[j].lp_state = port->lp_state; attr.lg_ports[j].lp_lacp_state = port->lp_lacp_state; where = (char *)(port + 1); } rc = fn(arg, &attr); free(attr.lg_ports); if (rc != 0) goto bail; } bail: free(ioc); (void) close(fd); 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 i_dladm_aggr_parse_db(char *line, dladm_aggr_grp_attr_db_t *attr) { char *token; int i; int value; char *endp = NULL; char *lasts = NULL; bzero(attr, sizeof (*attr)); /* key */ if ((token = strtok_r(line, " \t", &lasts)) == NULL) goto failed; errno = 0; value = (int)strtol(token, &endp, 10); if (errno != 0 || *endp != '\0') goto failed; attr->lt_key = value; /* policy */ if ((token = strtok_r(NULL, " \t", &lasts)) == NULL || !dladm_aggr_str2policy(token, &attr->lt_policy)) goto failed; /* number of ports */ if ((token = strtok_r(NULL, " \t", &lasts)) == NULL) return (-1); errno = 0; value = (int)strtol(token, &endp, 10); if (errno != 0 || *endp != '\0') goto failed; attr->lt_nports = value; /* ports */ if ((attr->lt_ports = malloc(attr->lt_nports * sizeof (dladm_aggr_port_attr_db_t))) == NULL) goto failed; for (i = 0; i < attr->lt_nports; i++) { char *where, *devname; /* port */ if ((token = strtok_r(NULL, ", \t\n", &lasts)) == NULL) goto failed; /* * device name: In a previous version of this file, a port * number could be specified using /. * This syntax is unecessary and obsolete. */ if ((devname = strtok_r(token, "/", &where)) == NULL) goto failed; if (strlcpy(attr->lt_ports[i].lp_devname, devname, MAXNAMELEN) >= MAXNAMELEN) goto failed; } /* unicast MAC address */ if ((token = strtok_r(NULL, " \t\n", &lasts)) == NULL || !dladm_aggr_str2macaddr(token, &attr->lt_mac_fixed, attr->lt_mac)) goto failed; /* LACP mode */ if ((token = strtok_r(NULL, " \t\n", &lasts)) == NULL || !dladm_aggr_str2lacpmode(token, &attr->lt_lacp_mode)) attr->lt_lacp_mode = AGGR_LACP_OFF; /* LACP timer */ if ((token = strtok_r(NULL, " \t\n", &lasts)) == NULL || !dladm_aggr_str2lacptimer(token, &attr->lt_lacp_timer)) attr->lt_lacp_timer = AGGR_LACP_TIMER_SHORT; return (0); failed: free(attr->lt_ports); attr->lt_ports = NULL; return (-1); } /* * Walk through the groups defined in the DB and for each group , * invoke (, ); */ static dladm_status_t i_dladm_aggr_walk_db(dladm_status_t (*fn)(void *, dladm_aggr_grp_attr_db_t *), void *arg, const char *root) { FILE *fp; char line[MAXLINELEN]; dladm_aggr_grp_attr_db_t attr; char *db_file; char db_file_buf[MAXPATHLEN]; int lock_fd; dladm_status_t status = DLADM_STATUS_OK; if (root == NULL) { db_file = DLADM_AGGR_DB; } else { (void) snprintf(db_file_buf, MAXPATHLEN, "%s%s", root, DLADM_AGGR_DB); db_file = db_file_buf; } lock_fd = i_dladm_aggr_lock_db(F_RDLCK); if ((fp = fopen(db_file, "r")) == NULL) { status = dladm_errno2status(errno); i_dladm_aggr_unlock_db(lock_fd); return (status); } bzero(&attr, sizeof (attr)); while (fgets(line, MAXLINELEN, fp) != NULL) { /* skip comments */ if (BLANK_LINE(line)) continue; if (i_dladm_aggr_parse_db(line, &attr) != 0) { status = DLADM_STATUS_REPOSITORYINVAL; goto done; } if ((status = fn(arg, &attr)) != DLADM_STATUS_OK) goto done; free(attr.lt_ports); attr.lt_ports = NULL; } done: free(attr.lt_ports); (void) fclose(fp); i_dladm_aggr_unlock_db(lock_fd); return (status); } /* * Send an add or remove command to the link aggregation driver. */ static dladm_status_t i_dladm_aggr_add_rem_sys(dladm_aggr_grp_attr_db_t *attr, int cmd) { int i, rc, fd, len; laioc_add_rem_t *iocp; laioc_port_t *ports; dladm_status_t status = DLADM_STATUS_OK; len = sizeof (*iocp) + attr->lt_nports * sizeof (laioc_port_t); iocp = malloc(len); if (iocp == NULL) { status = DLADM_STATUS_NOMEM; goto done; } iocp->la_key = attr->lt_key; iocp->la_nports = attr->lt_nports; ports = (laioc_port_t *)(iocp + 1); for (i = 0; i < attr->lt_nports; i++) { if (strlcpy(ports[i].lp_devname, attr->lt_ports[i].lp_devname, MAXNAMELEN) >= MAXNAMELEN) { status = DLADM_STATUS_BADARG; goto done; } } if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0) { status = dladm_errno2status(errno); goto done; } rc = i_dladm_aggr_strioctl(fd, cmd, iocp, len); if (rc < 0) { if (errno == EINVAL) status = DLADM_STATUS_LINKINVAL; else status = dladm_errno2status(errno); } (void) close(fd); done: free(iocp); return (status); } /* * Send a modify command to the link aggregation driver. */ static dladm_status_t i_dladm_aggr_modify_sys(uint32_t key, uint32_t mask, dladm_aggr_modify_attr_t *attr) { int rc, fd; laioc_modify_t ioc; dladm_status_t status = DLADM_STATUS_OK; ioc.lu_key = key; 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 ((fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0) return (dladm_errno2status(errno)); rc = i_dladm_aggr_strioctl(fd, LAIOC_MODIFY, &ioc, sizeof (ioc)); if (rc < 0) { if (errno == EINVAL) status = DLADM_STATUS_MACADDRINVAL; else status = dladm_errno2status(errno); } (void) close(fd); return (status); } /* * Send a create command to the link aggregation driver. */ static dladm_status_t i_dladm_aggr_create_sys(int fd, dladm_aggr_grp_attr_db_t *attr) { int i, rc, len; laioc_create_t *iocp; laioc_port_t *ports; dladm_status_t status = DLADM_STATUS_OK; len = sizeof (*iocp) + attr->lt_nports * sizeof (laioc_port_t); iocp = malloc(len); if (iocp == NULL) return (DLADM_STATUS_NOMEM); iocp->lc_key = attr->lt_key; iocp->lc_nports = attr->lt_nports; iocp->lc_policy = attr->lt_policy; iocp->lc_lacp_mode = attr->lt_lacp_mode; iocp->lc_lacp_timer = attr->lt_lacp_timer; ports = (laioc_port_t *)(iocp + 1); for (i = 0; i < attr->lt_nports; i++) { if (strlcpy(ports[i].lp_devname, attr->lt_ports[i].lp_devname, MAXNAMELEN) >= MAXNAMELEN) { free(iocp); return (DLADM_STATUS_BADARG); } } if (attr->lt_mac_fixed && ((bcmp(zero_mac, attr->lt_mac, ETHERADDRL) == 0) || (attr->lt_mac[0] & 0x01))) { free(iocp); return (DLADM_STATUS_MACADDRINVAL); } bcopy(attr->lt_mac, iocp->lc_mac, ETHERADDRL); iocp->lc_mac_fixed = attr->lt_mac_fixed; rc = i_dladm_aggr_strioctl(fd, LAIOC_CREATE, iocp, len); if (rc < 0) status = DLADM_STATUS_LINKINVAL; free(iocp); return (status); } /* * Invoked to bring up a link aggregation group. */ static dladm_status_t i_dladm_aggr_up(void *arg, dladm_aggr_grp_attr_db_t *attr) { dladm_aggr_up_t *up = (dladm_aggr_up_t *)arg; dladm_status_t status; if (up->lu_key != 0 && up->lu_key != attr->lt_key) return (DLADM_STATUS_OK); up->lu_found = B_TRUE; status = i_dladm_aggr_create_sys(up->lu_fd, attr); if (status != DLADM_STATUS_OK && up->lu_key != 0) 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 */ dladm_status_t dladm_aggr_up(uint32_t key, const char *root) { dladm_aggr_up_t up; dladm_status_t status; if ((up.lu_fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0) return (dladm_errno2status(errno)); up.lu_key = key; up.lu_found = B_FALSE; status = i_dladm_aggr_walk_db(i_dladm_aggr_up, &up, root); if (status != DLADM_STATUS_OK) { (void) close(up.lu_fd); return (status); } (void) close(up.lu_fd); /* * only return error if user specified key and key was * not found */ if (!up.lu_found && key != 0) return (DLADM_STATUS_NOTFOUND); return (DLADM_STATUS_OK); } /* * Send a delete command to the link aggregation driver. */ static int i_dladm_aggr_delete_sys(int fd, dladm_aggr_grp_attr_t *attr) { laioc_delete_t ioc; ioc.ld_key = attr->lg_key; return (i_dladm_aggr_strioctl(fd, LAIOC_DELETE, &ioc, sizeof (ioc))); } /* * Invoked to bring down a link aggregation group. */ static int i_dladm_aggr_down(void *arg, dladm_aggr_grp_attr_t *attr) { dladm_aggr_down_t *down = (dladm_aggr_down_t *)arg; int fd, errno_save; if (down->ld_key != 0 && down->ld_key != attr->lg_key) return (0); down->ld_found = B_TRUE; if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0) return (-1); if (i_dladm_aggr_delete_sys(fd, attr) < 0 && down->ld_key != 0) { errno_save = errno; (void) close(fd); errno = errno_save; return (-1); } (void) close(fd); 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 */ dladm_status_t dladm_aggr_down(uint32_t key) { dladm_aggr_down_t down; down.ld_key = key; down.ld_found = B_FALSE; if (dladm_aggr_walk(i_dladm_aggr_down, &down) < 0) return (dladm_errno2status(errno)); /* * only return error if user specified key and key was * not found */ if (!down.ld_found && key != 0) return (DLADM_STATUS_NOTFOUND); return (DLADM_STATUS_OK); } /* * For each group found in the DB, invokes (, ). * * The following values can be returned by (): * * -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 (), 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 i_dladm_aggr_walk_rw_db(int (*fn)(void *, dladm_aggr_grp_attr_db_t *), void *arg, const char *root) { FILE *fp, *nfp; int nfd, fn_rc, lock_fd; char line[MAXLINELEN]; dladm_aggr_grp_attr_db_t attr; char *db_file, *tmp_db_file; char db_file_buf[MAXPATHLEN]; char tmp_db_file_buf[MAXPATHLEN]; dladm_status_t status; if (root == NULL) { db_file = DLADM_AGGR_DB; tmp_db_file = DLADM_AGGR_DB_TMP; } else { (void) snprintf(db_file_buf, MAXPATHLEN, "%s%s", root, DLADM_AGGR_DB); (void) snprintf(tmp_db_file_buf, MAXPATHLEN, "%s%s", root, DLADM_AGGR_DB_TMP); db_file = db_file_buf; tmp_db_file = tmp_db_file_buf; } if ((lock_fd = i_dladm_aggr_lock_db(F_WRLCK)) < 0) return (dladm_errno2status(errno)); if ((fp = fopen(db_file, "r")) == NULL) { status = dladm_errno2status(errno); i_dladm_aggr_unlock_db(lock_fd); return (status); } if ((nfd = open(tmp_db_file, O_WRONLY|O_CREAT|O_TRUNC, DLADM_AGGR_DB_PERMS)) == -1) { status = dladm_errno2status(errno); (void) fclose(fp); i_dladm_aggr_unlock_db(lock_fd); return (status); } if ((nfp = fdopen(nfd, "w")) == NULL) { status = dladm_errno2status(errno); (void) close(nfd); (void) fclose(fp); (void) unlink(tmp_db_file); i_dladm_aggr_unlock_db(lock_fd); return (status); } attr.lt_ports = NULL; while (fgets(line, MAXLINELEN, fp) != NULL) { /* skip comments */ if (BLANK_LINE(line)) { if (fputs(line, nfp) == EOF) { status = dladm_errno2status(errno); goto failed; } continue; } if (i_dladm_aggr_parse_db(line, &attr) != 0) { status = DLADM_STATUS_REPOSITORYINVAL; goto failed; } fn_rc = fn(arg, &attr); switch (fn_rc) { case -1: /* failure, stop walking */ status = dladm_errno2status(errno); goto failed; case 0: /* * Success, write group attributes, which could * have been modified by fn(). */ if (i_dladm_aggr_fput_grp(nfp, &attr) != 0) { status = dladm_errno2status(errno); goto failed; } break; case 1: /* skip current group */ break; } free(attr.lt_ports); attr.lt_ports = NULL; } if (getuid() == 0 || geteuid() == 0) { if (fchmod(nfd, DLADM_AGGR_DB_PERMS) == -1) { status = dladm_errno2status(errno); goto failed; } if (fchown(nfd, DLADM_AGGR_DB_OWNER, DLADM_AGGR_DB_GROUP) == -1) { status = dladm_errno2status(errno); goto failed; } } if (fflush(nfp) == EOF) { status = dladm_errno2status(errno); goto failed; } (void) fclose(fp); (void) fclose(nfp); if (rename(tmp_db_file, db_file) == -1) { status = dladm_errno2status(errno); (void) unlink(tmp_db_file); i_dladm_aggr_unlock_db(lock_fd); return (status); } i_dladm_aggr_unlock_db(lock_fd); return (DLADM_STATUS_OK); failed: free(attr.lt_ports); (void) fclose(fp); (void) fclose(nfp); (void) unlink(tmp_db_file); i_dladm_aggr_unlock_db(lock_fd); return (status); } /* * Remove an entry from the DB. */ static int i_dladm_aggr_del_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp) { delete_db_state_t *state = arg; if (grp->lt_key != state->ds_key) return (0); state->ds_found = B_TRUE; /* don't save matching group */ return (1); } static dladm_status_t i_dladm_aggr_del_db(dladm_aggr_grp_attr_db_t *attr, const char *root) { delete_db_state_t state; dladm_status_t status; state.ds_key = attr->lt_key; state.ds_found = B_FALSE; status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_del_db_fn, &state, root); if (status != DLADM_STATUS_OK) return (status); if (!state.ds_found) return (DLADM_STATUS_NOTFOUND); return (DLADM_STATUS_OK); } /* * Modify the properties of an existing group in the DB. */ static int i_dladm_aggr_modify_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp) { modify_db_state_t *state = arg; dladm_aggr_modify_attr_t *new_attr = state->us_attr_new; dladm_aggr_modify_attr_t *old_attr = state->us_attr_old; if (grp->lt_key != state->us_key) return (0); state->us_found = B_TRUE; if (state->us_mask & DLADM_AGGR_MODIFY_POLICY) { if (old_attr != NULL) old_attr->ld_policy = grp->lt_policy; grp->lt_policy = new_attr->ld_policy; } if (state->us_mask & DLADM_AGGR_MODIFY_MAC) { if (old_attr != NULL) { old_attr->ld_mac_fixed = grp->lt_mac_fixed; bcopy(grp->lt_mac, old_attr->ld_mac, ETHERADDRL); } grp->lt_mac_fixed = new_attr->ld_mac_fixed; bcopy(new_attr->ld_mac, grp->lt_mac, ETHERADDRL); } if (state->us_mask & DLADM_AGGR_MODIFY_LACP_MODE) { if (old_attr != NULL) old_attr->ld_lacp_mode = grp->lt_lacp_mode; grp->lt_lacp_mode = new_attr->ld_lacp_mode; } if (state->us_mask & DLADM_AGGR_MODIFY_LACP_TIMER) { if (old_attr != NULL) old_attr->ld_lacp_timer = grp->lt_lacp_timer; grp->lt_lacp_timer = new_attr->ld_lacp_timer; } /* save modified group */ return (0); } static dladm_status_t i_dladm_aggr_modify_db(uint32_t key, uint32_t mask, dladm_aggr_modify_attr_t *new, dladm_aggr_modify_attr_t *old, const char *root) { modify_db_state_t state; dladm_status_t status; state.us_key = key; state.us_mask = mask; state.us_attr_new = new; state.us_attr_old = old; state.us_found = B_FALSE; if ((status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_modify_db_fn, &state, root)) != DLADM_STATUS_OK) { return (status); } if (!state.us_found) return (DLADM_STATUS_NOTFOUND); return (DLADM_STATUS_OK); } /* * Add ports to an existing group in the DB. */ static int i_dladm_aggr_add_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp) { add_db_state_t *state = arg; dladm_aggr_grp_attr_db_t *attr = state->as_attr; void *ports; int i, j; if (grp->lt_key != attr->lt_key) return (0); state->as_found = B_TRUE; /* are any of the ports to be added already members of the group? */ for (i = 0; i < grp->lt_nports; i++) { for (j = 0; j < attr->lt_nports; j++) { if (strcmp(grp->lt_ports[i].lp_devname, attr->lt_ports[j].lp_devname) == 0) { errno = EEXIST; return (-1); } } } /* add groups specified by attr to grp */ ports = realloc(grp->lt_ports, (grp->lt_nports + attr->lt_nports) * sizeof (dladm_aggr_port_attr_db_t)); if (ports == NULL) return (-1); grp->lt_ports = ports; for (i = 0; i < attr->lt_nports; i++) { if (strlcpy(grp->lt_ports[grp->lt_nports + i].lp_devname, attr->lt_ports[i].lp_devname, MAXNAMELEN + 1) >= MAXNAMELEN + 1) return (-1); } grp->lt_nports += attr->lt_nports; /* save modified group */ return (0); } static dladm_status_t i_dladm_aggr_add_db(dladm_aggr_grp_attr_db_t *attr, const char *root) { add_db_state_t state; dladm_status_t status; state.as_attr = attr; state.as_found = B_FALSE; status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_add_db_fn, &state, root); if (status != DLADM_STATUS_OK) return (status); if (!state.as_found) return (DLADM_STATUS_NOTFOUND); return (DLADM_STATUS_OK); } /* * Remove ports from an existing group in the DB. */ typedef struct remove_db_state { dladm_aggr_grp_attr_db_t *rs_attr; boolean_t rs_found; } remove_db_state_t; static int i_dladm_aggr_remove_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp) { remove_db_state_t *state = (remove_db_state_t *)arg; dladm_aggr_grp_attr_db_t *attr = state->rs_attr; int i, j, k, nremoved; boolean_t match; if (grp->lt_key != attr->lt_key) return (0); state->rs_found = B_TRUE; /* remove the ports specified by attr from the group */ nremoved = 0; k = 0; for (i = 0; i < grp->lt_nports; i++) { match = B_FALSE; for (j = 0; j < attr->lt_nports && !match; j++) { match = (strcmp(grp->lt_ports[i].lp_devname, attr->lt_ports[j].lp_devname) == 0); } if (match) nremoved++; else grp->lt_ports[k++] = grp->lt_ports[i]; } if (nremoved != attr->lt_nports) { errno = ENOENT; return (-1); } grp->lt_nports -= nremoved; /* save modified group */ return (0); } static dladm_status_t i_dladm_aggr_remove_db(dladm_aggr_grp_attr_db_t *attr, const char *root) { remove_db_state_t state; dladm_status_t status; state.rs_attr = attr; state.rs_found = B_FALSE; status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_remove_db_fn, &state, root); if (status != DLADM_STATUS_OK) return (status); if (!state.rs_found) 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. */ 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; str[0] = '\0'; for (i = 0; i < NPOLICIES; i++) { pol = &policies[i]; if ((policy & pol->policy) != 0) { npolicies++; if (npolicies > 1) (void) strcat(str, ","); (void) strcat(str, pol->pol_name); } } 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(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) return (gettext("")); else return (_link_ntoa(mac, buf, ETHERADDRL, IFT_OTHER)); } /* * 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; 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; 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; 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); } #define FPRINTF_ERR(fcall) if ((fcall) < 0) return (-1); /* * Write the attribute of a group to the specified file. Returns 0 on * success, -1 on failure. */ static int i_dladm_aggr_fput_grp(FILE *fp, dladm_aggr_grp_attr_db_t *attr) { int i; char addr_str[ETHERADDRL * 3]; char buf[DLADM_STRSIZE]; /* key, policy */ FPRINTF_ERR(fprintf(fp, "%d\t%s\t", attr->lt_key, dladm_aggr_policy2str(attr->lt_policy, buf))); /* number of ports, ports */ FPRINTF_ERR(fprintf(fp, "%d\t", attr->lt_nports)); for (i = 0; i < attr->lt_nports; i++) { if (i > 0) FPRINTF_ERR(fprintf(fp, ",")); FPRINTF_ERR(fprintf(fp, "%s", attr->lt_ports[i].lp_devname)); } FPRINTF_ERR(fprintf(fp, "\t")); /* MAC address */ if (!attr->lt_mac_fixed) { FPRINTF_ERR(fprintf(fp, "auto")); } else { FPRINTF_ERR(fprintf(fp, "%s", dladm_aggr_macaddr2str(attr->lt_mac, addr_str))); } FPRINTF_ERR(fprintf(fp, "\t")); FPRINTF_ERR(fprintf(fp, "%s\t", dladm_aggr_lacpmode2str(attr->lt_lacp_mode, buf))); FPRINTF_ERR(fprintf(fp, "%s\n", dladm_aggr_lacptimer2str(attr->lt_lacp_timer, buf))); return (0); } static dladm_status_t i_dladm_aggr_create_db(dladm_aggr_grp_attr_db_t *attr, const char *root) { FILE *fp; char line[MAXLINELEN]; uint32_t key; int lock_fd; char *db_file; char db_file_buf[MAXPATHLEN]; char *endp = NULL; dladm_status_t status; if (root == NULL) { db_file = DLADM_AGGR_DB; } else { (void) snprintf(db_file_buf, MAXPATHLEN, "%s%s", root, DLADM_AGGR_DB); db_file = db_file_buf; } if ((lock_fd = i_dladm_aggr_lock_db(F_WRLCK)) < 0) return (dladm_errno2status(errno)); if ((fp = fopen(db_file, "r+")) == NULL && (fp = fopen(db_file, "w")) == NULL) { status = dladm_errno2status(errno); i_dladm_aggr_unlock_db(lock_fd); return (status); } /* look for existing group with same key */ while (fgets(line, MAXLINELEN, fp) != NULL) { char *holder, *lasts; /* skip comments */ if (BLANK_LINE(line)) continue; /* ignore corrupted lines */ holder = strtok_r(line, " \t", &lasts); if (holder == NULL) continue; /* port number */ errno = 0; key = (int)strtol(holder, &endp, 10); if (errno != 0 || *endp != '\0') { status = DLADM_STATUS_REPOSITORYINVAL; goto done; } if (key == attr->lt_key) { /* group with key already exists */ status = DLADM_STATUS_EXIST; 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. */ if (i_dladm_aggr_fput_grp(fp, attr) != 0) { status = dladm_errno2status(errno); goto done; } status = DLADM_STATUS_OK; done: (void) fclose(fp); i_dladm_aggr_unlock_db(lock_fd); return (status); } /* * Create a new link aggregation group. Update the configuration * file and bring it up. */ dladm_status_t dladm_aggr_create(uint32_t key, uint32_t nports, dladm_aggr_port_attr_db_t *ports, uint32_t policy, boolean_t mac_addr_fixed, uchar_t *mac_addr, aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer, boolean_t tempop, const char *root) { dladm_aggr_grp_attr_db_t attr; dladm_status_t status; if (key == 0 || key > DLADM_AGGR_MAX_KEY) return (DLADM_STATUS_KEYINVAL); attr.lt_key = key; attr.lt_nports = nports; attr.lt_ports = ports; attr.lt_policy = policy; attr.lt_mac_fixed = mac_addr_fixed; if (attr.lt_mac_fixed) bcopy(mac_addr, attr.lt_mac, ETHERADDRL); else bzero(attr.lt_mac, ETHERADDRL); attr.lt_lacp_mode = lacp_mode; attr.lt_lacp_timer = lacp_timer; /* add the link aggregation group to the DB */ if (!tempop) { status = i_dladm_aggr_create_db(&attr, root); if (status != DLADM_STATUS_OK) return (status); } else { dladm_aggr_up_t up; up.lu_key = key; up.lu_found = B_FALSE; up.lu_fd = open(DLADM_AGGR_DEV, O_RDWR); if (up.lu_fd < 0) return (dladm_errno2status(errno)); status = i_dladm_aggr_up((void *)&up, &attr); (void) close(up.lu_fd); return (status); } /* bring up the link aggregation group */ status = dladm_aggr_up(key, root); /* * If the operation fails because the aggregation already exists, * then only update the persistent configuration repository and * return success. */ if (status == DLADM_STATUS_EXIST) status = DLADM_STATUS_OK; if (status != DLADM_STATUS_OK && !tempop) (void) i_dladm_aggr_del_db(&attr, root); 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(uint32_t key, uint32_t modify_mask, uint32_t policy, boolean_t mac_fixed, uchar_t *mac_addr, aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer, boolean_t tempop, const char *root) { dladm_aggr_modify_attr_t new_attr, old_attr; dladm_status_t status; if (key == 0) return (DLADM_STATUS_KEYINVAL); if (modify_mask & DLADM_AGGR_MODIFY_POLICY) new_attr.ld_policy = policy; if (modify_mask & DLADM_AGGR_MODIFY_MAC) { new_attr.ld_mac_fixed = mac_fixed; bcopy(mac_addr, new_attr.ld_mac, ETHERADDRL); } if (modify_mask & DLADM_AGGR_MODIFY_LACP_MODE) new_attr.ld_lacp_mode = lacp_mode; if (modify_mask & DLADM_AGGR_MODIFY_LACP_TIMER) new_attr.ld_lacp_timer = lacp_timer; /* update the DB */ if (!tempop && ((status = i_dladm_aggr_modify_db(key, modify_mask, &new_attr, &old_attr, root)) != DLADM_STATUS_OK)) { return (status); } status = i_dladm_aggr_modify_sys(key, modify_mask, &new_attr); if (status != DLADM_STATUS_OK && !tempop) { (void) i_dladm_aggr_modify_db(key, modify_mask, &old_attr, NULL, root); } return (status); } /* * Delete a previously created link aggregation group. */ dladm_status_t dladm_aggr_delete(uint32_t key, boolean_t tempop, const char *root) { dladm_aggr_grp_attr_db_t db_attr; dladm_status_t status; if (key == 0) return (DLADM_STATUS_KEYINVAL); if (tempop) { dladm_aggr_down_t down; dladm_aggr_grp_attr_t sys_attr; down.ld_key = key; down.ld_found = B_FALSE; sys_attr.lg_key = key; if (i_dladm_aggr_down((void *)&down, &sys_attr) < 0) return (dladm_errno2status(errno)); else return (DLADM_STATUS_OK); } else { status = dladm_aggr_down(key); /* * 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); db_attr.lt_key = key; return (i_dladm_aggr_del_db(&db_attr, root)); } /* * Add one or more ports to an existing link aggregation. */ dladm_status_t dladm_aggr_add(uint32_t key, uint32_t nports, dladm_aggr_port_attr_db_t *ports, boolean_t tempop, const char *root) { dladm_aggr_grp_attr_db_t attr; dladm_status_t status; if (key == 0) return (DLADM_STATUS_KEYINVAL); bzero(&attr, sizeof (attr)); attr.lt_key = key; attr.lt_nports = nports; attr.lt_ports = ports; if (!tempop && ((status = i_dladm_aggr_add_db(&attr, root)) != DLADM_STATUS_OK)) { return (status); } status = i_dladm_aggr_add_rem_sys(&attr, LAIOC_ADD); if (status != DLADM_STATUS_OK && !tempop) (void) i_dladm_aggr_remove_db(&attr, root); return (status); } /* * Remove one or more ports from an existing link aggregation. */ dladm_status_t dladm_aggr_remove(uint32_t key, uint32_t nports, dladm_aggr_port_attr_db_t *ports, boolean_t tempop, const char *root) { dladm_aggr_grp_attr_db_t attr; dladm_status_t status; if (key == 0) return (DLADM_STATUS_KEYINVAL); bzero(&attr, sizeof (attr)); attr.lt_key = key; attr.lt_nports = nports; attr.lt_ports = ports; if (!tempop && ((status = i_dladm_aggr_remove_db(&attr, root)) != DLADM_STATUS_OK)) { return (status); } status = i_dladm_aggr_add_rem_sys(&attr, LAIOC_REMOVE); if (status != DLADM_STATUS_OK && !tempop) (void) i_dladm_aggr_add_db(&attr, root); return (status); }