1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <stdio.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <string.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <stropts.h>
33 #include <stdlib.h>
34 #include <errno.h>
35 #include <assert.h>
36 #include <strings.h>
37 #include <libintl.h>
38 #include <net/if_types.h>
39 #include <net/if_dl.h>
40 #include <libdllink.h>
41 #include <libdlvlan.h>
42 #include <libdlaggr.h>
43 #include <libdladm_impl.h>
44 
45 /*
46  * Link Aggregation Administration Library.
47  *
48  * This library is used by administration tools such as dladm(1M) to
49  * configure link aggregations.
50  */
51 
52 /* Limits on buffer size for LAIOC_INFO request */
53 #define	MIN_INFO_SIZE (4*1024)
54 #define	MAX_INFO_SIZE (128*1024)
55 
56 static uchar_t	zero_mac[] = {0, 0, 0, 0, 0, 0};
57 #define	VALID_PORT_MAC(mac)						\
58 	(((mac) != NULL) && (bcmp(zero_mac, (mac), ETHERADDRL) != 0) &&	\
59 	(!(mac)[0] & 0x01))
60 
61 #define	PORT_DELIMITER	'.'
62 
63 #define	WRITE_PORT(portstr, portid, size) {			\
64 	char pstr[LINKID_STR_WIDTH + 2];			\
65 	(void) snprintf(pstr, LINKID_STR_WIDTH + 2, "%d%c",	\
66 	    (portid), PORT_DELIMITER);				\
67 	(void) strlcat((portstr), pstr, (size));		\
68 }
69 
70 #define	READ_PORT(portstr, portid, status) {			\
71 	errno = 0;						\
72 	(status) = DLADM_STATUS_OK;				\
73 	(portid) = (int)strtol((portstr), &(portstr), 10);	\
74 	if (errno != 0 || *(portstr) != PORT_DELIMITER) {	\
75 		(status) = DLADM_STATUS_REPOSITORYINVAL;	\
76 	} else {						\
77 		/* Skip the delimiter. */			\
78 		(portstr)++;					\
79 	}							\
80 }
81 
82 typedef struct dladm_aggr_modify_attr {
83 	uint32_t	ld_policy;
84 	boolean_t	ld_mac_fixed;
85 	uchar_t		ld_mac[ETHERADDRL];
86 	aggr_lacp_mode_t ld_lacp_mode;
87 	aggr_lacp_timer_t ld_lacp_timer;
88 } dladm_aggr_modify_attr_t;
89 
90 typedef struct policy_s {
91 	char		*pol_name;
92 	uint32_t	policy;
93 } policy_t;
94 
95 static policy_t policies[] = {
96 	{"L2",		AGGR_POLICY_L2},
97 	{"L3",		AGGR_POLICY_L3},
98 	{"L4",		AGGR_POLICY_L4}};
99 
100 #define	NPOLICIES	(sizeof (policies) / sizeof (policy_t))
101 
102 typedef struct dladm_aggr_lacpmode_s {
103 	char		*mode_str;
104 	aggr_lacp_mode_t mode_id;
105 } dladm_aggr_lacpmode_t;
106 
107 static dladm_aggr_lacpmode_t lacp_modes[] = {
108 	{"off", AGGR_LACP_OFF},
109 	{"active", AGGR_LACP_ACTIVE},
110 	{"passive", AGGR_LACP_PASSIVE}};
111 
112 #define	NLACP_MODES	(sizeof (lacp_modes) / sizeof (dladm_aggr_lacpmode_t))
113 
114 typedef struct dladm_aggr_lacptimer_s {
115 	char		*lt_str;
116 	aggr_lacp_timer_t lt_id;
117 } dladm_aggr_lacptimer_t;
118 
119 static dladm_aggr_lacptimer_t lacp_timers[] = {
120 	{"short", AGGR_LACP_TIMER_SHORT},
121 	{"long", AGGR_LACP_TIMER_LONG}};
122 
123 #define	NLACP_TIMERS	(sizeof (lacp_timers) / sizeof (dladm_aggr_lacptimer_t))
124 
125 typedef struct dladm_aggr_port_state {
126 	char			*state_str;
127 	aggr_port_state_t	state_id;
128 } dladm_aggr_port_state_t;
129 
130 static dladm_aggr_port_state_t port_states[] = {
131 	{"standby", AGGR_PORT_STATE_STANDBY },
132 	{"attached", AGGR_PORT_STATE_ATTACHED }
133 };
134 
135 #define	NPORT_STATES	\
136 	(sizeof (port_states) / sizeof (dladm_aggr_port_state_t))
137 
138 static int
139 i_dladm_aggr_ioctl(int cmd, void *ptr)
140 {
141 	int err, fd;
142 
143 	if ((fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0)
144 		return (-1);
145 
146 	err = ioctl(fd, cmd, ptr);
147 	(void) close(fd);
148 
149 	return (err);
150 }
151 
152 /*
153  * Caller must free attr.lg_ports. The ptr pointer is advanced while convert
154  * the laioc_info_t to the dladm_aggr_grp_attr_t structure.
155  */
156 static int
157 i_dladm_aggr_iocp2grpattr(void **ptr, dladm_aggr_grp_attr_t *attrp)
158 {
159 	laioc_info_group_t	*grp;
160 	laioc_info_port_t	*port;
161 	int			i;
162 	void			*where = (*ptr);
163 
164 	grp = (laioc_info_group_t *)where;
165 
166 	attrp->lg_linkid = grp->lg_linkid;
167 	attrp->lg_key = grp->lg_key;
168 	attrp->lg_nports = grp->lg_nports;
169 	attrp->lg_policy = grp->lg_policy;
170 	attrp->lg_lacp_mode = grp->lg_lacp_mode;
171 	attrp->lg_lacp_timer = grp->lg_lacp_timer;
172 	attrp->lg_force = grp->lg_force;
173 
174 	bcopy(grp->lg_mac, attrp->lg_mac, ETHERADDRL);
175 	attrp->lg_mac_fixed = grp->lg_mac_fixed;
176 
177 	if ((attrp->lg_ports = malloc(grp->lg_nports *
178 	    sizeof (dladm_aggr_port_attr_t))) == NULL) {
179 		errno = ENOMEM;
180 		goto fail;
181 	}
182 
183 	where = (grp + 1);
184 
185 	/*
186 	 * Go through each port that is part of the group.
187 	 */
188 	for (i = 0; i < grp->lg_nports; i++) {
189 		port = (laioc_info_port_t *)where;
190 
191 		attrp->lg_ports[i].lp_linkid = port->lp_linkid;
192 		bcopy(port->lp_mac, attrp->lg_ports[i].lp_mac, ETHERADDRL);
193 		attrp->lg_ports[i].lp_state = port->lp_state;
194 		attrp->lg_ports[i].lp_lacp_state = port->lp_lacp_state;
195 
196 		where = (port + 1);
197 	}
198 	*ptr = where;
199 	return (0);
200 fail:
201 	return (-1);
202 }
203 
204 /*
205  * Get active configuration of a specific aggregation.
206  * Caller must free attrp->la_ports.
207  */
208 static dladm_status_t
209 i_dladm_aggr_info_active(datalink_id_t linkid, dladm_aggr_grp_attr_t *attrp)
210 {
211 	laioc_info_t *ioc;
212 	int bufsize;
213 	void *where;
214 	dladm_status_t status = DLADM_STATUS_OK;
215 
216 	bufsize = MIN_INFO_SIZE;
217 	ioc = (laioc_info_t *)calloc(1, bufsize);
218 	if (ioc == NULL)
219 		return (DLADM_STATUS_NOMEM);
220 
221 	ioc->li_group_linkid = linkid;
222 
223 tryagain:
224 	ioc->li_bufsize = bufsize;
225 	if (i_dladm_aggr_ioctl(LAIOC_INFO, ioc) != 0) {
226 		if (errno == ENOSPC) {
227 			/*
228 			 * The LAIOC_INFO call failed due to a short
229 			 * buffer. Reallocate the buffer and try again.
230 			 */
231 			bufsize *= 2;
232 			if (bufsize <= MAX_INFO_SIZE) {
233 				ioc = (laioc_info_t *)realloc(ioc, bufsize);
234 				if (ioc != NULL) {
235 					bzero(ioc, sizeof (bufsize));
236 					goto tryagain;
237 				}
238 			}
239 		}
240 		status = dladm_errno2status(errno);
241 		goto bail;
242 	}
243 
244 	/*
245 	 * Go through each group returned by the aggregation driver.
246 	 */
247 	where = (char *)(ioc + 1);
248 	if (i_dladm_aggr_iocp2grpattr(&where, attrp) != 0) {
249 		status = dladm_errno2status(errno);
250 		goto bail;
251 	}
252 
253 bail:
254 	free(ioc);
255 	return (status);
256 }
257 
258 /*
259  * Get configuration information of a specific aggregation.
260  * Caller must free attrp->la_ports.
261  */
262 static dladm_status_t
263 i_dladm_aggr_info_persist(datalink_id_t linkid, dladm_aggr_grp_attr_t *attrp)
264 {
265 	dladm_conf_t	conf;
266 	uint32_t	nports, i;
267 	char		*portstr, *next;
268 	dladm_status_t	status;
269 	uint64_t	u64;
270 	int		size;
271 	char		macstr[ETHERADDRL * 3];
272 
273 	attrp->lg_linkid = linkid;
274 	if ((status = dladm_read_conf(linkid, &conf)) != DLADM_STATUS_OK)
275 		return (status);
276 
277 	status = dladm_get_conf_field(conf, FKEY, &u64, sizeof (u64));
278 	if (status != DLADM_STATUS_OK)
279 		goto done;
280 	attrp->lg_key = (uint16_t)u64;
281 
282 	status = dladm_get_conf_field(conf, FPOLICY, &u64, sizeof (u64));
283 	if (status != DLADM_STATUS_OK)
284 		goto done;
285 	attrp->lg_policy = (uint32_t)u64;
286 
287 	status = dladm_get_conf_field(conf, FFIXMACADDR, &attrp->lg_mac_fixed,
288 	    sizeof (boolean_t));
289 	if (status != DLADM_STATUS_OK)
290 		goto done;
291 
292 	if (attrp->lg_mac_fixed) {
293 		boolean_t fixed;
294 
295 		if ((status = dladm_get_conf_field(conf, FMACADDR, macstr,
296 		    sizeof (macstr))) != DLADM_STATUS_OK) {
297 			goto done;
298 		}
299 		if (!dladm_aggr_str2macaddr(macstr, &fixed, attrp->lg_mac)) {
300 			status = DLADM_STATUS_REPOSITORYINVAL;
301 			goto done;
302 		}
303 	}
304 
305 	status = dladm_get_conf_field(conf, FFORCE, &attrp->lg_force,
306 	    sizeof (boolean_t));
307 	if (status != DLADM_STATUS_OK)
308 		goto done;
309 
310 	status = dladm_get_conf_field(conf, FLACPMODE, &u64, sizeof (u64));
311 	if (status != DLADM_STATUS_OK)
312 		goto done;
313 	attrp->lg_lacp_mode = (aggr_lacp_mode_t)u64;
314 
315 	status = dladm_get_conf_field(conf, FLACPTIMER, &u64, sizeof (u64));
316 	if (status != DLADM_STATUS_OK)
317 		goto done;
318 	attrp->lg_lacp_timer = (aggr_lacp_timer_t)u64;
319 
320 	status = dladm_get_conf_field(conf, FNPORTS, &u64, sizeof (u64));
321 	if (status != DLADM_STATUS_OK)
322 		goto done;
323 	nports = (uint32_t)u64;
324 	attrp->lg_nports = nports;
325 
326 	size = nports * (LINKID_STR_WIDTH + 1) + 1;
327 	if ((portstr = calloc(1, size)) == NULL) {
328 		status = DLADM_STATUS_NOMEM;
329 		goto done;
330 	}
331 
332 	status = dladm_get_conf_field(conf, FPORTS, portstr, size);
333 	if (status != DLADM_STATUS_OK) {
334 		free(portstr);
335 		goto done;
336 	}
337 
338 	if ((attrp->lg_ports = malloc(nports *
339 	    sizeof (dladm_aggr_port_attr_t))) == NULL) {
340 		free(portstr);
341 		status = DLADM_STATUS_NOMEM;
342 		goto done;
343 	}
344 
345 	for (next = portstr, i = 0; i < nports; i++) {
346 		READ_PORT(next, attrp->lg_ports[i].lp_linkid, status);
347 		if (status != DLADM_STATUS_OK) {
348 			free(portstr);
349 			free(attrp->lg_ports);
350 			goto done;
351 		}
352 	}
353 	free(portstr);
354 
355 done:
356 	dladm_destroy_conf(conf);
357 	return (status);
358 }
359 
360 dladm_status_t
361 dladm_aggr_info(datalink_id_t linkid, dladm_aggr_grp_attr_t *attrp,
362     uint32_t flags)
363 {
364 	assert(flags == DLADM_OPT_ACTIVE || flags == DLADM_OPT_PERSIST);
365 	if (flags == DLADM_OPT_ACTIVE)
366 		return (i_dladm_aggr_info_active(linkid, attrp));
367 	else
368 		return (i_dladm_aggr_info_persist(linkid, attrp));
369 }
370 
371 /*
372  * Add or remove one or more ports to/from an existing link aggregation.
373  */
374 static dladm_status_t
375 i_dladm_aggr_add_rmv(datalink_id_t linkid, uint32_t nports,
376     dladm_aggr_port_attr_db_t *ports, uint32_t flags, int cmd)
377 {
378 	char *orig_portstr = NULL, *portstr = NULL;
379 	laioc_add_rem_t *iocp = NULL;
380 	laioc_port_t *ioc_ports;
381 	uint32_t orig_nports, result_nports, len, i, j;
382 	dladm_conf_t conf;
383 	datalink_class_t class;
384 	dladm_status_t status = DLADM_STATUS_OK;
385 	int size;
386 	uint64_t u64;
387 	uint32_t media;
388 
389 	if (nports == 0)
390 		return (DLADM_STATUS_BADARG);
391 
392 	/*
393 	 * Sanity check - aggregations can only be created over Ethernet
394 	 * physical links.
395 	 */
396 	for (i = 0; i < nports; i++) {
397 		if ((dladm_datalink_id2info(ports[i].lp_linkid, NULL,
398 		    &class, &media, NULL, 0) != DLADM_STATUS_OK) ||
399 		    (class != DATALINK_CLASS_PHYS) || (media != DL_ETHER)) {
400 			return (DLADM_STATUS_BADARG);
401 		}
402 	}
403 
404 	/*
405 	 * First, update the persistent configuration if requested.  We only
406 	 * need to update the FPORTS and FNPORTS fields of this aggregation.
407 	 * Note that FPORTS is a list of port linkids separated by
408 	 * PORT_DELIMITER ('.').
409 	 */
410 	if (flags & DLADM_OPT_PERSIST) {
411 		status = dladm_read_conf(linkid, &conf);
412 		if (status != DLADM_STATUS_OK)
413 			return (status);
414 
415 		/*
416 		 * Get the original configuration of FNPORTS and FPORTS.
417 		 */
418 		status = dladm_get_conf_field(conf, FNPORTS, &u64,
419 		    sizeof (u64));
420 		if (status != DLADM_STATUS_OK)
421 			goto destroyconf;
422 		orig_nports = (uint32_t)u64;
423 
424 		/*
425 		 * At least one port needs to be in the aggregation.
426 		 */
427 		if ((cmd == LAIOC_REMOVE) && (orig_nports <= nports)) {
428 			status = DLADM_STATUS_BADARG;
429 			goto destroyconf;
430 		}
431 
432 		size = orig_nports * (LINKID_STR_WIDTH + 1) + 1;
433 		if ((orig_portstr = calloc(1, size)) == NULL) {
434 			status = dladm_errno2status(errno);
435 			goto destroyconf;
436 		}
437 
438 		status = dladm_get_conf_field(conf, FPORTS, orig_portstr, size);
439 		if (status != DLADM_STATUS_OK)
440 			goto destroyconf;
441 
442 		result_nports = (cmd == LAIOC_ADD) ? orig_nports + nports :
443 		    orig_nports;
444 
445 		size = result_nports * (LINKID_STR_WIDTH + 1) + 1;
446 		if ((portstr = calloc(1, size)) == NULL) {
447 			status = dladm_errno2status(errno);
448 			goto destroyconf;
449 		}
450 
451 		/*
452 		 * get the new configuration and set to result_nports and
453 		 * portstr.
454 		 */
455 		if (cmd == LAIOC_ADD) {
456 			(void) strlcpy(portstr, orig_portstr, size);
457 			for (i = 0; i < nports; i++)
458 				WRITE_PORT(portstr, ports[i].lp_linkid, size);
459 		} else {
460 			char *next;
461 			datalink_id_t portid;
462 			uint32_t remove = 0;
463 
464 			for (next = orig_portstr, j = 0; j < orig_nports; j++) {
465 				/*
466 				 * Read the portids from the old configuration
467 				 * one by one.
468 				 */
469 				READ_PORT(next, portid, status);
470 				if (status != DLADM_STATUS_OK) {
471 					free(portstr);
472 					goto destroyconf;
473 				}
474 
475 				/*
476 				 * See whether this port is in the removal
477 				 * list.  If not, copy to the new config.
478 				 */
479 				for (i = 0; i < nports; i++) {
480 					if (ports[i].lp_linkid == portid)
481 						break;
482 				}
483 				if (i == nports) {
484 					WRITE_PORT(portstr, portid, size);
485 				} else {
486 					remove++;
487 				}
488 			}
489 			if (remove != nports) {
490 				status = DLADM_STATUS_LINKINVAL;
491 				free(portstr);
492 				goto destroyconf;
493 			}
494 			result_nports -= nports;
495 		}
496 
497 		u64 = result_nports;
498 		if ((status = dladm_set_conf_field(conf, FNPORTS,
499 		    DLADM_TYPE_UINT64, &u64)) != DLADM_STATUS_OK) {
500 			free(portstr);
501 			goto destroyconf;
502 		}
503 
504 		status = dladm_set_conf_field(conf, FPORTS, DLADM_TYPE_STR,
505 		    portstr);
506 		free(portstr);
507 		if (status != DLADM_STATUS_OK)
508 			goto destroyconf;
509 
510 		/*
511 		 * Write the new configuration to the persistent repository.
512 		 */
513 		status = dladm_write_conf(conf);
514 
515 destroyconf:
516 		dladm_destroy_conf(conf);
517 		if (status != DLADM_STATUS_OK) {
518 			free(orig_portstr);
519 			return (status);
520 		}
521 	}
522 
523 	/*
524 	 * If the caller only requested to update the persistent
525 	 * configuration, we are done.
526 	 */
527 	if (!(flags & DLADM_OPT_ACTIVE))
528 		goto done;
529 
530 	/*
531 	 * Update the active configuration.
532 	 */
533 	len = sizeof (*iocp) + nports * sizeof (laioc_port_t);
534 	if ((iocp = malloc(len)) == NULL) {
535 		status = DLADM_STATUS_NOMEM;
536 		goto done;
537 	}
538 
539 	iocp->la_linkid = linkid;
540 	iocp->la_nports = nports;
541 	if (cmd == LAIOC_ADD)
542 		iocp->la_force = (flags & DLADM_OPT_FORCE);
543 
544 	ioc_ports = (laioc_port_t *)(iocp + 1);
545 	for (i = 0; i < nports; i++)
546 		ioc_ports[i].lp_linkid = ports[i].lp_linkid;
547 
548 	if (i_dladm_aggr_ioctl(cmd, iocp) < 0)
549 		status = dladm_errno2status(errno);
550 
551 done:
552 	free(iocp);
553 
554 	/*
555 	 * If the active configuration update fails, restore the old
556 	 * persistent configuration if we've changed that.
557 	 */
558 	if ((status != DLADM_STATUS_OK) && (flags & DLADM_OPT_PERSIST)) {
559 		if (dladm_read_conf(linkid, &conf) == DLADM_STATUS_OK) {
560 			u64 = orig_nports;
561 			if ((dladm_set_conf_field(conf, FNPORTS,
562 			    DLADM_TYPE_UINT64, &u64) == DLADM_STATUS_OK) &&
563 			    (dladm_set_conf_field(conf, FPORTS, DLADM_TYPE_STR,
564 			    orig_portstr) == DLADM_STATUS_OK)) {
565 				(void) dladm_write_conf(conf);
566 			}
567 			(void) dladm_destroy_conf(conf);
568 		}
569 	}
570 	free(orig_portstr);
571 	return (status);
572 }
573 
574 /*
575  * Send a modify command to the link aggregation driver.
576  */
577 static dladm_status_t
578 i_dladm_aggr_modify_sys(datalink_id_t linkid, uint32_t mask,
579     dladm_aggr_modify_attr_t *attr)
580 {
581 	laioc_modify_t ioc;
582 
583 	ioc.lu_linkid = linkid;
584 
585 	ioc.lu_modify_mask = 0;
586 	if (mask & DLADM_AGGR_MODIFY_POLICY)
587 		ioc.lu_modify_mask |= LAIOC_MODIFY_POLICY;
588 	if (mask & DLADM_AGGR_MODIFY_MAC)
589 		ioc.lu_modify_mask |= LAIOC_MODIFY_MAC;
590 	if (mask & DLADM_AGGR_MODIFY_LACP_MODE)
591 		ioc.lu_modify_mask |= LAIOC_MODIFY_LACP_MODE;
592 	if (mask & DLADM_AGGR_MODIFY_LACP_TIMER)
593 		ioc.lu_modify_mask |= LAIOC_MODIFY_LACP_TIMER;
594 
595 	ioc.lu_policy = attr->ld_policy;
596 	ioc.lu_mac_fixed = attr->ld_mac_fixed;
597 	bcopy(attr->ld_mac, ioc.lu_mac, ETHERADDRL);
598 	ioc.lu_lacp_mode = attr->ld_lacp_mode;
599 	ioc.lu_lacp_timer = attr->ld_lacp_timer;
600 
601 	if (i_dladm_aggr_ioctl(LAIOC_MODIFY, &ioc) < 0) {
602 		if (errno == EINVAL)
603 			return (DLADM_STATUS_MACADDRINVAL);
604 		else
605 			return (dladm_errno2status(errno));
606 	} else {
607 		return (DLADM_STATUS_OK);
608 	}
609 }
610 
611 /*
612  * Send a create command to the link aggregation driver.
613  */
614 static dladm_status_t
615 i_dladm_aggr_create_sys(datalink_id_t linkid, uint16_t key, uint32_t nports,
616     dladm_aggr_port_attr_db_t *ports, uint32_t policy,
617     boolean_t mac_addr_fixed, const uchar_t *mac_addr,
618     aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer, boolean_t force)
619 {
620 	int i, len;
621 	laioc_create_t *iocp = NULL;
622 	laioc_port_t *ioc_ports;
623 	dladm_status_t status = DLADM_STATUS_OK;
624 
625 	len = sizeof (*iocp) + nports * sizeof (laioc_port_t);
626 	iocp = malloc(len);
627 	if (iocp == NULL)
628 		return (DLADM_STATUS_NOMEM);
629 
630 	iocp->lc_key = key;
631 	iocp->lc_linkid = linkid;
632 	iocp->lc_nports = nports;
633 	iocp->lc_policy = policy;
634 	iocp->lc_lacp_mode = lacp_mode;
635 	iocp->lc_lacp_timer = lacp_timer;
636 	ioc_ports = (laioc_port_t *)(iocp + 1);
637 	iocp->lc_force = force;
638 
639 	for (i = 0; i < nports; i++)
640 		ioc_ports[i].lp_linkid = ports[i].lp_linkid;
641 
642 	if (mac_addr_fixed && !VALID_PORT_MAC(mac_addr)) {
643 		status = DLADM_STATUS_MACADDRINVAL;
644 		goto done;
645 	}
646 
647 	bcopy(mac_addr, iocp->lc_mac, ETHERADDRL);
648 	iocp->lc_mac_fixed = mac_addr_fixed;
649 
650 	if (i_dladm_aggr_ioctl(LAIOC_CREATE, iocp) < 0)
651 		status = dladm_errno2status(errno);
652 
653 done:
654 	free(iocp);
655 	return (status);
656 }
657 
658 /*
659  * Invoked to bring up a link aggregation group.
660  */
661 static int
662 i_dladm_aggr_up(datalink_id_t linkid, void *arg)
663 {
664 	dladm_status_t *statusp = (dladm_status_t *)arg;
665 	dladm_aggr_grp_attr_t attr;
666 	dladm_aggr_port_attr_db_t *ports = NULL;
667 	uint16_t key = 0;
668 	int i, j;
669 	dladm_status_t status;
670 
671 	status = dladm_aggr_info(linkid, &attr, DLADM_OPT_PERSIST);
672 	if (status != DLADM_STATUS_OK) {
673 		*statusp = status;
674 		return (DLADM_WALK_CONTINUE);
675 	}
676 
677 	if (attr.lg_key <= AGGR_MAX_KEY)
678 		key = attr.lg_key;
679 
680 	ports = malloc(attr.lg_nports * sizeof (dladm_aggr_port_attr_db_t));
681 	if (ports == NULL) {
682 		status = DLADM_STATUS_NOMEM;
683 		goto done;
684 	}
685 
686 	/*
687 	 * Validate (and purge) each physical link associated with this
688 	 * aggregation, if the specific hardware has been removed during
689 	 * the system shutdown.
690 	 */
691 	for (i = 0, j = 0; i < attr.lg_nports; i++) {
692 		datalink_id_t	portid = attr.lg_ports[i].lp_linkid;
693 		uint32_t	flags;
694 		dladm_status_t	s;
695 
696 		s = dladm_datalink_id2info(portid, &flags, NULL, NULL, NULL, 0);
697 		if (s != DLADM_STATUS_OK || !(flags & DLADM_OPT_ACTIVE))
698 			continue;
699 
700 		ports[j++].lp_linkid = portid;
701 	}
702 
703 	if (j == 0) {
704 		/*
705 		 * All of the physical links are removed.
706 		 */
707 		status = DLADM_STATUS_BADARG;
708 		goto done;
709 	}
710 
711 	/*
712 	 * Create active aggregation.
713 	 */
714 	if ((status = i_dladm_aggr_create_sys(linkid,
715 	    key, j, ports, attr.lg_policy, attr.lg_mac_fixed,
716 	    (const uchar_t *)attr.lg_mac, attr.lg_lacp_mode,
717 	    attr.lg_lacp_timer, attr.lg_force)) != DLADM_STATUS_OK) {
718 		goto done;
719 	}
720 
721 	if ((status = dladm_up_datalink_id(linkid)) != DLADM_STATUS_OK) {
722 		laioc_delete_t ioc;
723 		ioc.ld_linkid = linkid;
724 		(void) i_dladm_aggr_ioctl(LAIOC_DELETE, &ioc);
725 		goto done;
726 	}
727 
728 	/*
729 	 * Reset the active linkprop of this specific link.
730 	 */
731 	(void) dladm_init_linkprop(linkid, B_FALSE);
732 
733 done:
734 	free(attr.lg_ports);
735 	free(ports);
736 
737 	*statusp = status;
738 	return (DLADM_WALK_CONTINUE);
739 }
740 
741 /*
742  * Bring up one aggregation, or all persistent aggregations.  In the latter
743  * case, the walk may terminate early if bringup of an aggregation fails.
744  */
745 dladm_status_t
746 dladm_aggr_up(datalink_id_t linkid)
747 {
748 	dladm_status_t status;
749 
750 	if (linkid == DATALINK_ALL_LINKID) {
751 		(void) dladm_walk_datalink_id(i_dladm_aggr_up, &status,
752 		    DATALINK_CLASS_AGGR, DATALINK_ANY_MEDIATYPE,
753 		    DLADM_OPT_PERSIST);
754 		return (DLADM_STATUS_OK);
755 	} else {
756 		(void) i_dladm_aggr_up(linkid, &status);
757 		return (status);
758 	}
759 }
760 
761 /*
762  * Given a policy string, return a policy mask. Returns B_TRUE on
763  * success, or B_FALSE if an error occurred during parsing.
764  */
765 boolean_t
766 dladm_aggr_str2policy(const char *str, uint32_t *policy)
767 {
768 	int i;
769 	policy_t *pol;
770 	char *token = NULL;
771 	char *lasts;
772 
773 	*policy = 0;
774 
775 	while ((token = strtok_r((token == NULL) ? (char *)str : NULL, ",",
776 	    &lasts)) != NULL) {
777 		for (i = 0; i < NPOLICIES; i++) {
778 			pol = &policies[i];
779 			if (strcasecmp(token, pol->pol_name) == 0) {
780 				*policy |= pol->policy;
781 				break;
782 			}
783 		}
784 		if (i == NPOLICIES)
785 			return (B_FALSE);
786 	}
787 
788 	return (B_TRUE);
789 }
790 
791 /*
792  * Given a policy mask, returns a printable string, or NULL if the
793  * policy mask is invalid. It is the responsibility of the caller to
794  * free the returned string after use.
795  */
796 char *
797 dladm_aggr_policy2str(uint32_t policy, char *str)
798 {
799 	int i, npolicies = 0;
800 	policy_t *pol;
801 
802 	if (str == NULL)
803 		return (NULL);
804 
805 	str[0] = '\0';
806 
807 	for (i = 0; i < NPOLICIES; i++) {
808 		pol = &policies[i];
809 		if ((policy & pol->policy) != 0) {
810 			npolicies++;
811 			if (npolicies > 1)
812 				(void) strlcat(str, ",", DLADM_STRSIZE);
813 			(void) strlcat(str, pol->pol_name, DLADM_STRSIZE);
814 		}
815 	}
816 
817 	return (str);
818 }
819 
820 /*
821  * Given a MAC address string, return the MAC address in the mac_addr
822  * array. If the MAC address was not explicitly specified, i.e. is
823  * equal to 'auto', zero out mac-addr and set mac_fixed to B_TRUE.
824  * Return B_FALSE if a syntax error was encountered, B_FALSE otherwise.
825  */
826 boolean_t
827 dladm_aggr_str2macaddr(const char *str, boolean_t *mac_fixed, uchar_t *mac_addr)
828 {
829 	uchar_t *conv_str;
830 	int mac_len;
831 
832 	*mac_fixed = (strcmp(str, "auto") != 0);
833 	if (!*mac_fixed) {
834 		bzero(mac_addr, ETHERADDRL);
835 		return (B_TRUE);
836 	}
837 
838 	conv_str = _link_aton(str, &mac_len);
839 	if (conv_str == NULL)
840 		return (B_FALSE);
841 
842 	if (mac_len != ETHERADDRL) {
843 		free(conv_str);
844 		return (B_FALSE);
845 	}
846 
847 	if ((bcmp(zero_mac, conv_str, ETHERADDRL) == 0) ||
848 	    (conv_str[0] & 0x01)) {
849 		free(conv_str);
850 		return (B_FALSE);
851 	}
852 
853 	bcopy(conv_str, mac_addr, ETHERADDRL);
854 	free(conv_str);
855 
856 	return (B_TRUE);
857 }
858 
859 /*
860  * Returns a string containing a printable representation of a MAC address.
861  */
862 const char *
863 dladm_aggr_macaddr2str(const unsigned char *mac, char *buf)
864 {
865 	static char unknown_mac[] = {0, 0, 0, 0, 0, 0};
866 
867 	if (buf == NULL)
868 		return (NULL);
869 
870 	if (bcmp(unknown_mac, mac, ETHERADDRL) == 0)
871 		(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
872 	else
873 		return (_link_ntoa(mac, buf, ETHERADDRL, IFT_OTHER));
874 
875 	return (buf);
876 }
877 
878 /*
879  * Given a LACP mode string, find the corresponding LACP mode number. Returns
880  * B_TRUE if a match was found, B_FALSE otherwise.
881  */
882 boolean_t
883 dladm_aggr_str2lacpmode(const char *str, aggr_lacp_mode_t *lacp_mode)
884 {
885 	int i;
886 	dladm_aggr_lacpmode_t *mode;
887 
888 	for (i = 0; i < NLACP_MODES; i++) {
889 		mode = &lacp_modes[i];
890 		if (strncasecmp(str, mode->mode_str,
891 		    strlen(mode->mode_str)) == 0) {
892 			*lacp_mode = mode->mode_id;
893 			return (B_TRUE);
894 		}
895 	}
896 
897 	return (B_FALSE);
898 }
899 
900 /*
901  * Given a LACP mode number, returns a printable string, or NULL if the
902  * LACP mode number is invalid.
903  */
904 const char *
905 dladm_aggr_lacpmode2str(aggr_lacp_mode_t mode_id, char *buf)
906 {
907 	int i;
908 	dladm_aggr_lacpmode_t *mode;
909 
910 	if (buf == NULL)
911 		return (NULL);
912 
913 	for (i = 0; i < NLACP_MODES; i++) {
914 		mode = &lacp_modes[i];
915 		if (mode->mode_id == mode_id) {
916 			(void) snprintf(buf, DLADM_STRSIZE, "%s",
917 			    mode->mode_str);
918 			return (buf);
919 		}
920 	}
921 
922 	(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
923 	return (buf);
924 }
925 
926 /*
927  * Given a LACP timer string, find the corresponding LACP timer number. Returns
928  * B_TRUE if a match was found, B_FALSE otherwise.
929  */
930 boolean_t
931 dladm_aggr_str2lacptimer(const char *str, aggr_lacp_timer_t *lacp_timer)
932 {
933 	int i;
934 	dladm_aggr_lacptimer_t *timer;
935 
936 	for (i = 0; i < NLACP_TIMERS; i++) {
937 		timer = &lacp_timers[i];
938 		if (strncasecmp(str, timer->lt_str,
939 		    strlen(timer->lt_str)) == 0) {
940 			*lacp_timer = timer->lt_id;
941 			return (B_TRUE);
942 		}
943 	}
944 
945 	return (B_FALSE);
946 }
947 
948 /*
949  * Given a LACP timer, returns a printable string, or NULL if the
950  * LACP timer number is invalid.
951  */
952 const char *
953 dladm_aggr_lacptimer2str(aggr_lacp_timer_t timer_id, char *buf)
954 {
955 	int i;
956 	dladm_aggr_lacptimer_t *timer;
957 
958 	if (buf == NULL)
959 		return (NULL);
960 
961 	for (i = 0; i < NLACP_TIMERS; i++) {
962 		timer = &lacp_timers[i];
963 		if (timer->lt_id == timer_id) {
964 			(void) snprintf(buf, DLADM_STRSIZE, "%s",
965 			    timer->lt_str);
966 			return (buf);
967 		}
968 	}
969 
970 	(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
971 	return (buf);
972 }
973 
974 const char *
975 dladm_aggr_portstate2str(aggr_port_state_t state_id, char *buf)
976 {
977 	int			i;
978 	dladm_aggr_port_state_t *state;
979 
980 	if (buf == NULL)
981 		return (NULL);
982 
983 	for (i = 0; i < NPORT_STATES; i++) {
984 		state = &port_states[i];
985 		if (state->state_id == state_id) {
986 			(void) snprintf(buf, DLADM_STRSIZE, "%s",
987 			    state->state_str);
988 			return (buf);
989 		}
990 	}
991 
992 	(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
993 	return (buf);
994 }
995 
996 static dladm_status_t
997 dladm_aggr_persist_aggr_conf(const char *link, datalink_id_t linkid,
998     uint16_t key, uint32_t nports, dladm_aggr_port_attr_db_t *ports,
999     uint32_t policy, boolean_t mac_addr_fixed, const uchar_t *mac_addr,
1000     aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer,
1001     boolean_t force)
1002 {
1003 	dladm_conf_t conf = DLADM_INVALID_CONF;
1004 	char *portstr = NULL;
1005 	char macstr[ETHERADDRL * 3];
1006 	dladm_status_t status;
1007 	int i, size;
1008 	uint64_t u64;
1009 
1010 	if ((status = dladm_create_conf(link, linkid, DATALINK_CLASS_AGGR,
1011 	    DL_ETHER, &conf)) != DLADM_STATUS_OK) {
1012 		return (status);
1013 	}
1014 
1015 	u64 = key;
1016 	status = dladm_set_conf_field(conf, FKEY, DLADM_TYPE_UINT64, &u64);
1017 	if (status != DLADM_STATUS_OK)
1018 		goto done;
1019 
1020 	u64 = nports;
1021 	status = dladm_set_conf_field(conf, FNPORTS, DLADM_TYPE_UINT64, &u64);
1022 	if (status != DLADM_STATUS_OK)
1023 		goto done;
1024 
1025 	size = nports * (LINKID_STR_WIDTH + 1) + 1;
1026 	if ((portstr = calloc(1, size)) == NULL) {
1027 		status = DLADM_STATUS_NOMEM;
1028 		goto done;
1029 	}
1030 
1031 	for (i = 0; i < nports; i++)
1032 		WRITE_PORT(portstr, ports[i].lp_linkid, size);
1033 	status = dladm_set_conf_field(conf, FPORTS, DLADM_TYPE_STR, portstr);
1034 	free(portstr);
1035 
1036 	if (status != DLADM_STATUS_OK)
1037 		goto done;
1038 
1039 	u64 = policy;
1040 	status = dladm_set_conf_field(conf, FPOLICY, DLADM_TYPE_UINT64, &u64);
1041 	if (status != DLADM_STATUS_OK)
1042 		goto done;
1043 
1044 	status = dladm_set_conf_field(conf, FFIXMACADDR, DLADM_TYPE_BOOLEAN,
1045 	    &mac_addr_fixed);
1046 	if (status != DLADM_STATUS_OK)
1047 		goto done;
1048 
1049 	if (mac_addr_fixed) {
1050 		if (!VALID_PORT_MAC(mac_addr)) {
1051 			status = DLADM_STATUS_MACADDRINVAL;
1052 			goto done;
1053 		}
1054 
1055 		(void) dladm_aggr_macaddr2str(mac_addr, macstr);
1056 		status = dladm_set_conf_field(conf, FMACADDR, DLADM_TYPE_STR,
1057 		    macstr);
1058 		if (status != DLADM_STATUS_OK)
1059 			goto done;
1060 	}
1061 
1062 	status = dladm_set_conf_field(conf, FFORCE, DLADM_TYPE_BOOLEAN, &force);
1063 	if (status != DLADM_STATUS_OK)
1064 		goto done;
1065 
1066 	u64 = lacp_mode;
1067 	status = dladm_set_conf_field(conf, FLACPMODE, DLADM_TYPE_UINT64, &u64);
1068 	if (status != DLADM_STATUS_OK)
1069 		goto done;
1070 
1071 	u64 = lacp_timer;
1072 	status = dladm_set_conf_field(conf, FLACPTIMER, DLADM_TYPE_UINT64,
1073 	    &u64);
1074 	if (status != DLADM_STATUS_OK)
1075 		goto done;
1076 
1077 	/*
1078 	 * Commit the link aggregation configuration.
1079 	 */
1080 	status = dladm_write_conf(conf);
1081 
1082 done:
1083 	dladm_destroy_conf(conf);
1084 	return (status);
1085 }
1086 
1087 /*
1088  * Create a new link aggregation group. Update the configuration
1089  * file and bring it up.
1090  */
1091 dladm_status_t
1092 dladm_aggr_create(const char *name, uint16_t key, uint32_t nports,
1093     dladm_aggr_port_attr_db_t *ports, uint32_t policy, boolean_t mac_addr_fixed,
1094     const uchar_t *mac_addr, aggr_lacp_mode_t lacp_mode,
1095     aggr_lacp_timer_t lacp_timer, uint32_t flags)
1096 {
1097 	datalink_id_t linkid = DATALINK_INVALID_LINKID;
1098 	uint32_t media;
1099 	uint32_t i;
1100 	datalink_class_t class;
1101 	dladm_status_t status;
1102 	boolean_t force = (flags & DLADM_OPT_FORCE) ? B_TRUE : B_FALSE;
1103 
1104 	if (key != 0 && key > AGGR_MAX_KEY)
1105 		return (DLADM_STATUS_KEYINVAL);
1106 
1107 	if (nports == 0)
1108 		return (DLADM_STATUS_BADARG);
1109 
1110 	for (i = 0; i < nports; i++) {
1111 		if ((dladm_datalink_id2info(ports[i].lp_linkid, NULL,
1112 		    &class, &media, NULL, 0) != DLADM_STATUS_OK) ||
1113 		    (class != DATALINK_CLASS_PHYS) && (media != DL_ETHER)) {
1114 			return (DLADM_STATUS_BADARG);
1115 		}
1116 	}
1117 
1118 	flags &= (DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST);
1119 	if ((status = dladm_create_datalink_id(name, DATALINK_CLASS_AGGR,
1120 	    DL_ETHER, flags, &linkid)) != DLADM_STATUS_OK) {
1121 		goto fail;
1122 	}
1123 
1124 	if ((flags & DLADM_OPT_PERSIST) &&
1125 	    (status = dladm_aggr_persist_aggr_conf(name, linkid, key, nports,
1126 	    ports, policy, mac_addr_fixed, mac_addr, lacp_mode, lacp_timer,
1127 	    force)) != DLADM_STATUS_OK) {
1128 		goto fail;
1129 	}
1130 
1131 	if (!(flags & DLADM_OPT_ACTIVE))
1132 		return (DLADM_STATUS_OK);
1133 
1134 	status = i_dladm_aggr_create_sys(linkid, key, nports, ports, policy,
1135 	    mac_addr_fixed, mac_addr, lacp_mode, lacp_timer, force);
1136 
1137 	if (status != DLADM_STATUS_OK) {
1138 		if (flags & DLADM_OPT_PERSIST)
1139 			(void) dladm_remove_conf(linkid);
1140 		goto fail;
1141 	}
1142 
1143 	return (DLADM_STATUS_OK);
1144 
1145 fail:
1146 	if (linkid != DATALINK_INVALID_LINKID)
1147 		(void) dladm_destroy_datalink_id(linkid, flags);
1148 
1149 	return (status);
1150 }
1151 
1152 static dladm_status_t
1153 i_dladm_aggr_get_aggr_attr(dladm_conf_t conf, uint32_t mask,
1154     dladm_aggr_modify_attr_t *attrp)
1155 {
1156 	dladm_status_t status = DLADM_STATUS_OK;
1157 	char macstr[ETHERADDRL * 3];
1158 	uint64_t u64;
1159 
1160 	if (mask & DLADM_AGGR_MODIFY_POLICY) {
1161 		status = dladm_get_conf_field(conf, FPOLICY, &u64,
1162 		    sizeof (u64));
1163 		if (status != DLADM_STATUS_OK)
1164 			return (status);
1165 		attrp->ld_policy = (uint32_t)u64;
1166 	}
1167 
1168 	if (mask & DLADM_AGGR_MODIFY_MAC) {
1169 		status = dladm_get_conf_field(conf, FFIXMACADDR,
1170 		    &attrp->ld_mac_fixed, sizeof (boolean_t));
1171 		if (status != DLADM_STATUS_OK)
1172 			return (status);
1173 
1174 		if (attrp->ld_mac_fixed) {
1175 			boolean_t fixed;
1176 
1177 			status = dladm_get_conf_field(conf, FMACADDR,
1178 			    macstr, sizeof (macstr));
1179 			if (status != DLADM_STATUS_OK)
1180 				return (status);
1181 
1182 			if (!dladm_aggr_str2macaddr(macstr, &fixed,
1183 			    attrp->ld_mac)) {
1184 				return (DLADM_STATUS_REPOSITORYINVAL);
1185 			}
1186 		}
1187 	}
1188 
1189 	if (mask & DLADM_AGGR_MODIFY_LACP_MODE) {
1190 		status = dladm_get_conf_field(conf, FLACPMODE, &u64,
1191 		    sizeof (u64));
1192 		if (status != DLADM_STATUS_OK)
1193 			return (status);
1194 		attrp->ld_lacp_mode = (aggr_lacp_mode_t)u64;
1195 	}
1196 
1197 	if (mask & DLADM_AGGR_MODIFY_LACP_TIMER) {
1198 		status = dladm_get_conf_field(conf, FLACPTIMER, &u64,
1199 		    sizeof (u64));
1200 		if (status != DLADM_STATUS_OK)
1201 			return (status);
1202 		attrp->ld_lacp_timer = (aggr_lacp_timer_t)u64;
1203 	}
1204 
1205 	return (status);
1206 }
1207 
1208 static dladm_status_t
1209 i_dladm_aggr_set_aggr_attr(dladm_conf_t conf, uint32_t mask,
1210     dladm_aggr_modify_attr_t *attrp)
1211 {
1212 	dladm_status_t status = DLADM_STATUS_OK;
1213 	char macstr[ETHERADDRL * 3];
1214 	uint64_t u64;
1215 
1216 	if (mask & DLADM_AGGR_MODIFY_POLICY) {
1217 		u64 = attrp->ld_policy;
1218 		status = dladm_set_conf_field(conf, FPOLICY, DLADM_TYPE_UINT64,
1219 		    &u64);
1220 		if (status != DLADM_STATUS_OK)
1221 			return (status);
1222 	}
1223 
1224 	if (mask & DLADM_AGGR_MODIFY_MAC) {
1225 		status = dladm_set_conf_field(conf, FFIXMACADDR,
1226 		    DLADM_TYPE_BOOLEAN, &attrp->ld_mac_fixed);
1227 		if (status != DLADM_STATUS_OK)
1228 			return (status);
1229 
1230 		if (attrp->ld_mac_fixed) {
1231 			(void) dladm_aggr_macaddr2str(attrp->ld_mac, macstr);
1232 			status = dladm_set_conf_field(conf, FMACADDR,
1233 			    DLADM_TYPE_STR, macstr);
1234 			if (status != DLADM_STATUS_OK)
1235 				return (status);
1236 		}
1237 	}
1238 
1239 	if (mask & DLADM_AGGR_MODIFY_LACP_MODE) {
1240 		u64 = attrp->ld_lacp_mode;
1241 		status = dladm_set_conf_field(conf, FLACPMODE,
1242 		    DLADM_TYPE_UINT64, &u64);
1243 		if (status != DLADM_STATUS_OK)
1244 			return (status);
1245 	}
1246 
1247 	if (mask & DLADM_AGGR_MODIFY_LACP_TIMER) {
1248 		u64 = attrp->ld_lacp_timer;
1249 		status = dladm_set_conf_field(conf, FLACPTIMER,
1250 		    DLADM_TYPE_UINT64, &u64);
1251 		if (status != DLADM_STATUS_OK)
1252 			return (status);
1253 	}
1254 
1255 	return (status);
1256 }
1257 
1258 /*
1259  * Modify the parameters of an existing link aggregation group. Update
1260  * the configuration file and pass the changes to the kernel.
1261  */
1262 dladm_status_t
1263 dladm_aggr_modify(datalink_id_t linkid, uint32_t modify_mask, uint32_t policy,
1264     boolean_t mac_fixed, const uchar_t *mac_addr, aggr_lacp_mode_t lacp_mode,
1265     aggr_lacp_timer_t lacp_timer, uint32_t flags)
1266 {
1267 	dladm_aggr_modify_attr_t new_attr, old_attr;
1268 	dladm_conf_t conf;
1269 	dladm_status_t status;
1270 
1271 	new_attr.ld_policy = policy;
1272 	new_attr.ld_mac_fixed = mac_fixed;
1273 	new_attr.ld_lacp_mode = lacp_mode;
1274 	new_attr.ld_lacp_timer = lacp_timer;
1275 	bcopy(mac_addr, new_attr.ld_mac, ETHERADDRL);
1276 
1277 	if (flags & DLADM_OPT_PERSIST) {
1278 		status = dladm_read_conf(linkid, &conf);
1279 		if (status != DLADM_STATUS_OK)
1280 			return (status);
1281 
1282 		if ((status = i_dladm_aggr_get_aggr_attr(conf, modify_mask,
1283 		    &old_attr)) != DLADM_STATUS_OK) {
1284 			goto done;
1285 		}
1286 
1287 		if ((status = i_dladm_aggr_set_aggr_attr(conf, modify_mask,
1288 		    &new_attr)) != DLADM_STATUS_OK) {
1289 			goto done;
1290 		}
1291 
1292 		status = dladm_write_conf(conf);
1293 
1294 done:
1295 		dladm_destroy_conf(conf);
1296 		if (status != DLADM_STATUS_OK)
1297 			return (status);
1298 	}
1299 
1300 	if (!(flags & DLADM_OPT_ACTIVE))
1301 		return (DLADM_STATUS_OK);
1302 
1303 	status = i_dladm_aggr_modify_sys(linkid, modify_mask, &new_attr);
1304 	if ((status != DLADM_STATUS_OK) && (flags & DLADM_OPT_PERSIST)) {
1305 		if (dladm_read_conf(linkid, &conf) == DLADM_STATUS_OK) {
1306 			if (i_dladm_aggr_set_aggr_attr(conf, modify_mask,
1307 			    &old_attr) == DLADM_STATUS_OK) {
1308 				(void) dladm_write_conf(conf);
1309 			}
1310 			dladm_destroy_conf(conf);
1311 		}
1312 	}
1313 
1314 	return (status);
1315 }
1316 
1317 typedef struct aggr_held_arg_s {
1318 	datalink_id_t	aggrid;
1319 	boolean_t	isheld;
1320 } aggr_held_arg_t;
1321 
1322 static int
1323 i_dladm_aggr_is_held(datalink_id_t linkid, void *arg)
1324 {
1325 	aggr_held_arg_t		*aggr_held_arg = arg;
1326 	dladm_vlan_attr_t	dva;
1327 
1328 	if (dladm_vlan_info(linkid, &dva, DLADM_OPT_PERSIST) != DLADM_STATUS_OK)
1329 		return (DLADM_WALK_CONTINUE);
1330 
1331 	if (dva.dv_linkid == aggr_held_arg->aggrid) {
1332 		/*
1333 		 * This VLAN is created over the given aggregation.
1334 		 */
1335 		aggr_held_arg->isheld = B_TRUE;
1336 		return (DLADM_WALK_TERMINATE);
1337 	}
1338 	return (DLADM_WALK_CONTINUE);
1339 }
1340 
1341 /*
1342  * Delete a previously created link aggregation group. Either the name "aggr"
1343  * or the "key" is specified.
1344  */
1345 dladm_status_t
1346 dladm_aggr_delete(datalink_id_t linkid, uint32_t flags)
1347 {
1348 	laioc_delete_t ioc;
1349 	datalink_class_t class;
1350 	dladm_status_t status;
1351 
1352 	if ((dladm_datalink_id2info(linkid, NULL, &class, NULL, NULL, 0) !=
1353 	    DLADM_STATUS_OK) || (class != DATALINK_CLASS_AGGR)) {
1354 		return (DLADM_STATUS_BADARG);
1355 	}
1356 
1357 	if (flags & DLADM_OPT_ACTIVE) {
1358 		ioc.ld_linkid = linkid;
1359 		if ((i_dladm_aggr_ioctl(LAIOC_DELETE, &ioc) < 0) &&
1360 		    ((errno != ENOENT) || !(flags & DLADM_OPT_PERSIST))) {
1361 			status = dladm_errno2status(errno);
1362 			return (status);
1363 		}
1364 
1365 		/*
1366 		 * Delete ACTIVE linkprop first.
1367 		 */
1368 		(void) dladm_set_linkprop(linkid, NULL, NULL, 0,
1369 		    DLADM_OPT_ACTIVE);
1370 		(void) dladm_destroy_datalink_id(linkid, DLADM_OPT_ACTIVE);
1371 	}
1372 
1373 	/*
1374 	 * If we reach here, it means that the active aggregation has already
1375 	 * been deleted, and there is no active VLANs holding this aggregation.
1376 	 * Now we see whether there is any persistent VLANs holding this
1377 	 * aggregation. If so, we fail the operation.
1378 	 */
1379 	if (flags & DLADM_OPT_PERSIST) {
1380 		aggr_held_arg_t arg;
1381 
1382 		arg.aggrid = linkid;
1383 		arg.isheld = B_FALSE;
1384 
1385 		(void) dladm_walk_datalink_id(i_dladm_aggr_is_held,
1386 		    &arg, DATALINK_CLASS_VLAN, DATALINK_ANY_MEDIATYPE,
1387 		    DLADM_OPT_PERSIST);
1388 		if (arg.isheld)
1389 			return (DLADM_STATUS_LINKBUSY);
1390 
1391 		(void) dladm_destroy_datalink_id(linkid, DLADM_OPT_PERSIST);
1392 		(void) dladm_remove_conf(linkid);
1393 	}
1394 
1395 	return (DLADM_STATUS_OK);
1396 }
1397 
1398 /*
1399  * Add one or more ports to an existing link aggregation.
1400  */
1401 dladm_status_t
1402 dladm_aggr_add(datalink_id_t linkid, uint32_t nports,
1403     dladm_aggr_port_attr_db_t *ports, uint32_t flags)
1404 {
1405 	return (i_dladm_aggr_add_rmv(linkid, nports, ports, flags, LAIOC_ADD));
1406 }
1407 
1408 /*
1409  * Remove one or more ports from an existing link aggregation.
1410  */
1411 dladm_status_t
1412 dladm_aggr_remove(datalink_id_t linkid, uint32_t nports,
1413     dladm_aggr_port_attr_db_t *ports, uint32_t flags)
1414 {
1415 	return (i_dladm_aggr_add_rmv(linkid, nports, ports, flags,
1416 	    LAIOC_REMOVE));
1417 }
1418 
1419 typedef struct i_walk_key_state_s {
1420 	uint16_t key;
1421 	datalink_id_t linkid;
1422 	boolean_t found;
1423 } i_walk_key_state_t;
1424 
1425 static int
1426 i_dladm_walk_key2linkid(datalink_id_t linkid, void *arg)
1427 {
1428 	dladm_conf_t conf;
1429 	uint16_t key;
1430 	dladm_status_t status;
1431 	i_walk_key_state_t *statep = (i_walk_key_state_t *)arg;
1432 	uint64_t u64;
1433 
1434 	if (dladm_read_conf(linkid, &conf) != 0)
1435 		return (DLADM_WALK_CONTINUE);
1436 
1437 	status = dladm_get_conf_field(conf, FKEY, &u64, sizeof (u64));
1438 	key = (uint16_t)u64;
1439 	dladm_destroy_conf(conf);
1440 
1441 	if ((status == DLADM_STATUS_OK) && (key == statep->key)) {
1442 		statep->found = B_TRUE;
1443 		statep->linkid = linkid;
1444 		return (DLADM_WALK_TERMINATE);
1445 	}
1446 
1447 	return (DLADM_WALK_CONTINUE);
1448 }
1449 
1450 dladm_status_t
1451 dladm_key2linkid(uint16_t key, datalink_id_t *linkidp, uint32_t flags)
1452 {
1453 	i_walk_key_state_t state;
1454 
1455 	if (key > AGGR_MAX_KEY)
1456 		return (DLADM_STATUS_NOTFOUND);
1457 
1458 	state.found = B_FALSE;
1459 	state.key = key;
1460 
1461 	(void) dladm_walk_datalink_id(i_dladm_walk_key2linkid, &state,
1462 	    DATALINK_CLASS_AGGR, DATALINK_ANY_MEDIATYPE, flags);
1463 	if (state.found == B_TRUE) {
1464 		*linkidp = state.linkid;
1465 		return (DLADM_STATUS_OK);
1466 	} else {
1467 		return (DLADM_STATUS_NOTFOUND);
1468 	}
1469 }
1470