xref: /titanic_44/usr/src/lib/libvrrpadm/common/libvrrpadm.c (revision 1cb875ae88fb9463b368e725c2444776595895cb)
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 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <sys/socket.h>
30 #include <sys/mman.h>
31 #include <sys/varargs.h>
32 #include <sys/vlan.h>
33 #include <errno.h>
34 #include <ctype.h>
35 #include <fcntl.h>
36 #include <unistd.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42 #include <net/if.h>	/* LIFNAMSIZ */
43 #include <netinet/vrrp.h>
44 #include <libdladm.h>
45 #include <libdlvnic.h>
46 #include <libdlvlan.h>
47 #include <libdllink.h>
48 #include <libintl.h>
49 #include <libvrrpadm.h>
50 
51 typedef vrrp_err_t vrrp_cmd_func_t(int, void *);
52 
53 static vrrp_err_t
54 vrrp_cmd_request(void *cmd, size_t csize, vrrp_cmd_func_t func, void *arg)
55 {
56 	struct sockaddr_un	to;
57 	int			sock, flags;
58 	size_t			len, cur_size = 0;
59 	vrrp_ret_t		ret;
60 	vrrp_err_t		err;
61 
62 	if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
63 		return (VRRP_ECMD);
64 
65 	/*
66 	 * Set it to be non-blocking.
67 	 */
68 	flags = fcntl(sock, F_GETFL, 0);
69 	(void) fcntl(sock, F_SETFL, (flags | O_NONBLOCK));
70 
71 	(void) memset(&to, 0, sizeof (to));
72 	to.sun_family = AF_UNIX;
73 	(void) strlcpy(to.sun_path, VRRPD_SOCKET, sizeof (to.sun_path));
74 
75 	/*
76 	 * Connect to vrrpd
77 	 */
78 	if (connect(sock, (const struct sockaddr *)&to, sizeof (to)) < 0) {
79 		(void) close(sock);
80 		return (VRRP_ECMD);
81 	}
82 
83 	/*
84 	 * Send the request
85 	 */
86 	while (cur_size < csize) {
87 		len = write(sock, (char *)cmd + cur_size, csize - cur_size);
88 		if (len == (size_t)-1 && errno == EAGAIN) {
89 			continue;
90 		} else if (len > 0) {
91 			cur_size += len;
92 			continue;
93 		}
94 		(void) close(sock);
95 		return (VRRP_ECMD);
96 	}
97 
98 	/*
99 	 * Expect the ack, first get the error code.
100 	 */
101 	cur_size = 0;
102 	while (cur_size < sizeof (vrrp_err_t)) {
103 		len = read(sock, (char *)&ret + cur_size,
104 		    sizeof (vrrp_err_t) - cur_size);
105 
106 		if (len == (size_t)-1 && errno == EAGAIN) {
107 			continue;
108 		} else if (len > 0) {
109 			cur_size += len;
110 			continue;
111 		}
112 		(void) close(sock);
113 		return (VRRP_ECMD);
114 	}
115 
116 	if ((err = ret.vr_err) != VRRP_SUCCESS)
117 		goto done;
118 
119 	/*
120 	 * The specific callback gets the rest of the information.
121 	 */
122 	if (func != NULL)
123 		err = func(sock, arg);
124 
125 done:
126 	(void) close(sock);
127 	return (err);
128 }
129 
130 /*
131  * public APIs
132  */
133 const char *
134 vrrp_err2str(vrrp_err_t err)
135 {
136 	switch (err) {
137 	case VRRP_SUCCESS:
138 		return (dgettext(TEXT_DOMAIN, "success"));
139 	case VRRP_ENOMEM:
140 		return (dgettext(TEXT_DOMAIN, "not enough memory"));
141 	case VRRP_EINVALVRNAME:
142 		return (dgettext(TEXT_DOMAIN, "invalid router name"));
143 	case VRRP_ENOPRIM:
144 		return (dgettext(TEXT_DOMAIN, "no primary IP"));
145 	case VRRP_EEXIST:
146 		return (dgettext(TEXT_DOMAIN, "already exists"));
147 	case VRRP_ENOVIRT:
148 		return (dgettext(TEXT_DOMAIN, "no virtual IPs"));
149 	case VRRP_EIPADM:
150 		return (dgettext(TEXT_DOMAIN, "ip configuration failure"));
151 	case VRRP_EDLADM:
152 		return (dgettext(TEXT_DOMAIN, "data-link configuration "
153 		    "failure"));
154 	case VRRP_EDB:
155 		return (dgettext(TEXT_DOMAIN, "configuration update error"));
156 	case VRRP_EBADSTATE:
157 		return (dgettext(TEXT_DOMAIN, "invalid state"));
158 	case VRRP_EVREXIST:
159 		return (dgettext(TEXT_DOMAIN, "VRRP router already exists"));
160 	case VRRP_ETOOSMALL:
161 		return (dgettext(TEXT_DOMAIN, "not enough space"));
162 	case VRRP_EINSTEXIST:
163 		return (dgettext(TEXT_DOMAIN, "router name already exists"));
164 	case VRRP_ENOTFOUND:
165 		return (dgettext(TEXT_DOMAIN, "VRRP router not found"));
166 	case VRRP_ECMD:
167 		return (dgettext(TEXT_DOMAIN, "failed to communicate to "
168 		    "vrrpd"));
169 	case VRRP_EINVALADDR:
170 		return (dgettext(TEXT_DOMAIN, "invalid IP address"));
171 	case VRRP_EINVALAF:
172 		return (dgettext(TEXT_DOMAIN, "invalid IP address family"));
173 	case VRRP_EINVALLINK:
174 		return (dgettext(TEXT_DOMAIN, "invalid data-link"));
175 	case VRRP_EPERM:
176 		return (dgettext(TEXT_DOMAIN, "permission denied"));
177 	case VRRP_ESYS:
178 		return (dgettext(TEXT_DOMAIN, "system error"));
179 	case VRRP_EAGAIN:
180 		return (dgettext(TEXT_DOMAIN, "try again"));
181 	case VRRP_EALREADY:
182 		return (dgettext(TEXT_DOMAIN, "operation already in progress"));
183 	case VRRP_ENOVNIC:
184 		return (dgettext(TEXT_DOMAIN, "VRRP VNIC has not been "
185 		    "created"));
186 	case VRRP_ENOLINK:
187 		return (dgettext(TEXT_DOMAIN, "the data-link does not exist"));
188 	case VRRP_EINVAL:
189 	default:
190 		return (dgettext(TEXT_DOMAIN, "invalid argument"));
191 	}
192 }
193 
194 const char *
195 vrrp_state2str(vrrp_state_t state)
196 {
197 	switch (state) {
198 	case VRRP_STATE_NONE:
199 		return (dgettext(TEXT_DOMAIN, "NONE"));
200 	case VRRP_STATE_INIT:
201 		return (dgettext(TEXT_DOMAIN, "INIT"));
202 	case VRRP_STATE_MASTER:
203 		return (dgettext(TEXT_DOMAIN, "MASTER"));
204 	case VRRP_STATE_BACKUP:
205 		return (dgettext(TEXT_DOMAIN, "BACKUP"));
206 	default:
207 		return (dgettext(TEXT_DOMAIN, "INVALID"));
208 	}
209 }
210 
211 vrrp_err_t
212 vrrp_open(vrrp_handle_t *vh)
213 {
214 	dladm_handle_t	dh;
215 
216 	if (dladm_open(&dh) != DLADM_STATUS_OK)
217 		return (VRRP_EDLADM);
218 
219 	if ((*vh = malloc(sizeof (struct vrrp_handle))) == NULL) {
220 		dladm_close(dh);
221 		return (VRRP_ENOMEM);
222 	}
223 	(*vh)->vh_dh = dh;
224 	return (VRRP_SUCCESS);
225 }
226 
227 void
228 vrrp_close(vrrp_handle_t vh)
229 {
230 	if (vh != NULL) {
231 		dladm_close(vh->vh_dh);
232 		free(vh);
233 	}
234 }
235 
236 boolean_t
237 vrrp_valid_name(const char *name)
238 {
239 	const char	*c;
240 
241 	/*
242 	 * The legal characters in a valid router name are:
243 	 * alphanumeric (a-z,  A-Z,  0-9), underscore ('_'), and '.'.
244 	 */
245 	for (c = name; *c != '\0'; c++) {
246 		if ((isalnum(*c) == 0) && (*c != '_'))
247 			return (B_FALSE);
248 	}
249 
250 	return (B_TRUE);
251 }
252 
253 /*ARGSUSED*/
254 vrrp_err_t
255 vrrp_create(vrrp_handle_t vh, vrrp_vr_conf_t *conf)
256 {
257 	vrrp_cmd_create_t	cmd;
258 	vrrp_err_t		err;
259 
260 	cmd.vcc_cmd = VRRP_CMD_CREATE;
261 	(void) memcpy(&cmd.vcc_conf, conf, sizeof (vrrp_vr_conf_t));
262 
263 	err = vrrp_cmd_request(&cmd, sizeof (cmd), NULL, NULL);
264 	return (err);
265 }
266 
267 /*ARGSUSED*/
268 vrrp_err_t
269 vrrp_delete(vrrp_handle_t vh, const char *vn)
270 {
271 	vrrp_cmd_delete_t	cmd;
272 	vrrp_err_t		err;
273 
274 	cmd.vcd_cmd = VRRP_CMD_DELETE;
275 	if (strlcpy(cmd.vcd_name, vn, VRRP_NAME_MAX) >= VRRP_NAME_MAX)
276 		return (VRRP_EINVAL);
277 
278 	err = vrrp_cmd_request(&cmd, sizeof (cmd), NULL, NULL);
279 	return (err);
280 }
281 
282 /*ARGSUSED*/
283 vrrp_err_t
284 vrrp_enable(vrrp_handle_t vh, const char *vn)
285 {
286 	vrrp_cmd_enable_t	cmd;
287 	vrrp_err_t		err;
288 
289 	cmd.vcs_cmd = VRRP_CMD_ENABLE;
290 	if (strlcpy(cmd.vcs_name, vn, VRRP_NAME_MAX) >= VRRP_NAME_MAX)
291 		return (VRRP_EINVAL);
292 
293 	err = vrrp_cmd_request(&cmd, sizeof (cmd), NULL, NULL);
294 	return (err);
295 }
296 
297 /*ARGSUSED*/
298 vrrp_err_t
299 vrrp_disable(vrrp_handle_t vh, const char *vn)
300 {
301 	vrrp_cmd_disable_t	cmd;
302 	vrrp_err_t		err;
303 
304 	cmd.vcx_cmd = VRRP_CMD_DISABLE;
305 	if (strlcpy(cmd.vcx_name, vn, VRRP_NAME_MAX) >= VRRP_NAME_MAX)
306 		return (VRRP_EINVAL);
307 
308 	err = vrrp_cmd_request(&cmd, sizeof (cmd), NULL, NULL);
309 	return (err);
310 }
311 
312 /*ARGSUSED*/
313 vrrp_err_t
314 vrrp_modify(vrrp_handle_t vh, vrrp_vr_conf_t *conf, uint32_t mask)
315 {
316 	vrrp_cmd_modify_t	cmd;
317 	vrrp_err_t		err;
318 
319 	cmd.vcm_cmd = VRRP_CMD_MODIFY;
320 	cmd.vcm_mask = mask;
321 	(void) memcpy(&cmd.vcm_conf, conf, sizeof (vrrp_vr_conf_t));
322 
323 	err = vrrp_cmd_request(&cmd, sizeof (cmd), NULL, NULL);
324 	return (err);
325 }
326 
327 typedef struct vrrp_cmd_list_arg {
328 	uint32_t	*vfl_cnt;
329 	char		*vfl_names;
330 } vrrp_cmd_list_arg_t;
331 
332 static vrrp_err_t
333 vrrp_list_func(int sock, void *arg)
334 {
335 	vrrp_cmd_list_arg_t	*list_arg = arg;
336 	uint32_t		in_cnt = *(list_arg->vfl_cnt);
337 	uint32_t		out_cnt;
338 	vrrp_ret_list_t		ret;
339 	size_t			len, cur_size = 0;
340 
341 	/*
342 	 * Get the rest of vrrp_ret_list_t besides the error code.
343 	 */
344 	cur_size = sizeof (vrrp_err_t);
345 	while (cur_size < sizeof (vrrp_ret_list_t)) {
346 		len = read(sock, (char *)&ret + cur_size,
347 		    sizeof (vrrp_ret_list_t) - cur_size);
348 
349 		if (len == (size_t)-1 && errno == EAGAIN) {
350 			continue;
351 		} else if (len > 0) {
352 			cur_size += len;
353 			continue;
354 		}
355 		return (VRRP_ECMD);
356 	}
357 
358 	*(list_arg->vfl_cnt) = out_cnt = ret.vrl_cnt;
359 	out_cnt = (in_cnt <= out_cnt) ? in_cnt : out_cnt;
360 	cur_size = 0;
361 
362 	while (cur_size < VRRP_NAME_MAX * out_cnt) {
363 		len = read(sock, (char *)list_arg->vfl_names + cur_size,
364 		    VRRP_NAME_MAX * out_cnt - cur_size);
365 
366 		if (len == (size_t)-1 && errno == EAGAIN) {
367 			continue;
368 		} else if (len > 0) {
369 			cur_size += len;
370 			continue;
371 		}
372 		return (VRRP_ECMD);
373 	}
374 	return (VRRP_SUCCESS);
375 }
376 
377 /*
378  * Looks up the vrrp instances that matches the given variable.
379  *
380  * If the given cnt is 0, names should be set to NULL. In this case, only
381  * the count of the matched instances is returned.
382  *
383  * If the given cnt is non-zero, caller must allocate "names" whose size
384  * is (cnt * VRRP_NAME_MAX).
385  *
386  * Return value: the current count of matched instances, and names will be
387  * points to the list of the current vrrp instances names. Note that
388  * only MIN(in_cnt, out_cnt) number of names will be returned.
389  */
390 /*ARGSUSED*/
391 vrrp_err_t
392 vrrp_list(vrrp_handle_t vh, vrid_t vrid, const char *intf, int af,
393     uint32_t *cnt, char *names)
394 {
395 	vrrp_cmd_list_t		cmd;
396 	vrrp_err_t		err;
397 	vrrp_cmd_list_arg_t	list_arg;
398 
399 	if ((cnt == NULL) || (*cnt != 0 && names == NULL))
400 		return (VRRP_EINVAL);
401 
402 	cmd.vcl_ifname[0] = '\0';
403 	if (intf != NULL && (strlcpy(cmd.vcl_ifname, intf,
404 	    LIFNAMSIZ) >= LIFNAMSIZ)) {
405 		return (VRRP_EINVAL);
406 	}
407 
408 	cmd.vcl_cmd = VRRP_CMD_LIST;
409 	cmd.vcl_vrid = vrid;
410 	cmd.vcl_af = af;
411 
412 	list_arg.vfl_cnt = cnt;
413 	list_arg.vfl_names = names;
414 
415 	err = vrrp_cmd_request(&cmd, sizeof (cmd), vrrp_list_func, &list_arg);
416 	return (err);
417 }
418 
419 static vrrp_err_t
420 vrrp_query_func(int sock, void *arg)
421 {
422 	vrrp_queryinfo_t	*qinfo = arg;
423 	size_t			len, cur_size = 0, total;
424 	uint32_t		in_cnt = qinfo->show_va.va_vipcnt;
425 	uint32_t		out_cnt;
426 
427 	/*
428 	 * Expect the ack, first get the vrrp_ret_t.
429 	 */
430 	total = sizeof (vrrp_queryinfo_t);
431 	while (cur_size < total) {
432 		len = read(sock, (char *)qinfo + cur_size, total - cur_size);
433 		if (len == (size_t)-1 && errno == EAGAIN) {
434 			continue;
435 		} else if (len > 0) {
436 			cur_size += len;
437 			continue;
438 		}
439 		return (VRRP_ECMD);
440 	}
441 
442 	out_cnt = qinfo->show_va.va_vipcnt;
443 
444 	/*
445 	 * Even if there is no IP virtual IP address, there is always
446 	 * space in the vrrp_queryinfo_t structure for one virtual
447 	 * IP address.
448 	 */
449 	out_cnt = (out_cnt == 0) ? 1 : out_cnt;
450 	out_cnt = (in_cnt < out_cnt ? in_cnt : out_cnt) - 1;
451 	total += out_cnt * sizeof (vrrp_addr_t);
452 
453 	while (cur_size < total) {
454 		len = read(sock, (char *)qinfo + cur_size, total - cur_size);
455 		if (len == (size_t)-1 && errno == EAGAIN) {
456 			continue;
457 		} else if (len > 0) {
458 			cur_size += len;
459 			continue;
460 		}
461 		return (VRRP_ECMD);
462 	}
463 	return (VRRP_SUCCESS);
464 }
465 
466 /*
467  * *vqp is allocated inside this function and must be freed by the caller.
468  */
469 /*ARGSUSED*/
470 vrrp_err_t
471 vrrp_query(vrrp_handle_t vh, const char *vn, vrrp_queryinfo_t **vqp)
472 {
473 	vrrp_cmd_query_t	cmd;
474 	vrrp_queryinfo_t	*qinfo;
475 	vrrp_err_t		err;
476 	size_t			size;
477 	uint32_t		vipcnt = 1;
478 
479 	if (strlcpy(cmd.vcq_name, vn, VRRP_NAME_MAX) >= VRRP_NAME_MAX)
480 		return (VRRP_EINVAL);
481 
482 	cmd.vcq_cmd = VRRP_CMD_QUERY;
483 
484 	/*
485 	 * Allocate enough room for virtual IPs.
486 	 */
487 again:
488 	size = sizeof (vrrp_queryinfo_t);
489 	size += (vipcnt == 0) ? 0 : (vipcnt - 1) * sizeof (vrrp_addr_t);
490 	if ((qinfo = malloc(size)) == NULL) {
491 		err = VRRP_ENOMEM;
492 		goto done;
493 	}
494 
495 	qinfo->show_va.va_vipcnt = vipcnt;
496 	err = vrrp_cmd_request(&cmd, sizeof (cmd), vrrp_query_func, qinfo);
497 	if (err != VRRP_SUCCESS) {
498 		free(qinfo);
499 		goto done;
500 	}
501 
502 	/*
503 	 * If the returned number of virtual IPs is greater than we expected,
504 	 * allocate more room and try again.
505 	 */
506 	if (qinfo->show_va.va_vipcnt > vipcnt) {
507 		vipcnt = qinfo->show_va.va_vipcnt;
508 		free(qinfo);
509 		goto again;
510 	}
511 
512 	*vqp = qinfo;
513 
514 done:
515 	return (err);
516 }
517 
518 struct lookup_vnic_arg {
519 	vrid_t		lva_vrid;
520 	datalink_id_t	lva_linkid;
521 	int		lva_af;
522 	uint16_t	lva_vid;
523 	vrrp_handle_t	lva_vh;
524 	char		lva_vnic[MAXLINKNAMELEN];
525 };
526 
527 /*
528  * Is this a special VNIC interface created for VRRP? If so, return
529  * the linkid the VNIC was created on, the VRRP ID and address family.
530  */
531 boolean_t
532 vrrp_is_vrrp_vnic(vrrp_handle_t vh, datalink_id_t vnicid,
533     datalink_id_t *linkidp, uint16_t *vidp, vrid_t *vridp, int *afp)
534 {
535 	dladm_vnic_attr_t	vattr;
536 
537 	if (dladm_vnic_info(vh->vh_dh, vnicid, &vattr, DLADM_OPT_ACTIVE) !=
538 	    DLADM_STATUS_OK) {
539 		return (B_FALSE);
540 	}
541 
542 	*vridp = vattr.va_vrid;
543 	*vidp = vattr.va_vid;
544 	*afp = vattr.va_af;
545 	*linkidp = vattr.va_link_id;
546 	return (vattr.va_vrid != VRRP_VRID_NONE);
547 }
548 
549 static int
550 lookup_vnic(dladm_handle_t dh, datalink_id_t vnicid, void *arg)
551 {
552 	vrid_t			vrid;
553 	uint16_t		vid;
554 	datalink_id_t		linkid;
555 	int			af;
556 	struct lookup_vnic_arg	*lva = arg;
557 
558 	if (vrrp_is_vrrp_vnic(lva->lva_vh, vnicid, &linkid, &vid, &vrid,
559 	    &af) && lva->lva_vrid == vrid && lva->lva_linkid == linkid &&
560 	    lva->lva_vid == vid && lva->lva_af == af) {
561 		if (dladm_datalink_id2info(dh, vnicid, NULL, NULL, NULL,
562 		    lva->lva_vnic, sizeof (lva->lva_vnic)) == DLADM_STATUS_OK) {
563 			return (DLADM_WALK_TERMINATE);
564 		}
565 	}
566 	return (DLADM_WALK_CONTINUE);
567 }
568 
569 /*
570  * Given the primary link name, find the assoicated VRRP vnic name, if
571  * the vnic does not exist yet, return the linkid, vid of the primary link.
572  */
573 vrrp_err_t
574 vrrp_get_vnicname(vrrp_handle_t vh, vrid_t vrid, int af, char *link,
575     datalink_id_t *linkidp, uint16_t *vidp, char *vnic, size_t len)
576 {
577 	datalink_id_t		linkid;
578 	uint32_t		flags;
579 	uint16_t		vid = VLAN_ID_NONE;
580 	datalink_class_t	class;
581 	dladm_vlan_attr_t	vlan_attr;
582 	struct lookup_vnic_arg	lva;
583 	uint32_t		media;
584 
585 	if ((strlen(link) == 0) || dladm_name2info(vh->vh_dh,
586 	    link, &linkid, &flags, &class, &media) !=
587 	    DLADM_STATUS_OK || !(flags & DLADM_OPT_ACTIVE)) {
588 		return (VRRP_EINVAL);
589 	}
590 
591 	if (class == DATALINK_CLASS_VLAN) {
592 		if (dladm_vlan_info(vh->vh_dh, linkid, &vlan_attr,
593 		    DLADM_OPT_ACTIVE) != DLADM_STATUS_OK) {
594 			return (VRRP_EINVAL);
595 		}
596 		linkid = vlan_attr.dv_linkid;
597 		vid = vlan_attr.dv_vid;
598 		if ((dladm_datalink_id2info(vh->vh_dh, linkid, NULL,
599 		    &class, &media, NULL, 0)) != DLADM_STATUS_OK) {
600 			return (VRRP_EINVAL);
601 		}
602 	}
603 
604 	/*
605 	 * For now, Only VRRP over aggr and physical ethernet links is supported
606 	 */
607 	if ((class != DATALINK_CLASS_PHYS && class != DATALINK_CLASS_AGGR) ||
608 	    media != DL_ETHER) {
609 		return (VRRP_EINVAL);
610 	}
611 
612 	if (linkidp != NULL)
613 		*linkidp = linkid;
614 	if (vidp != NULL)
615 		*vidp = vid;
616 
617 	/*
618 	 * Find the assoicated vnic with the given vrid/vid/af/linkid
619 	 */
620 	lva.lva_vrid = vrid;
621 	lva.lva_vid = vid;
622 	lva.lva_af = af;
623 	lva.lva_linkid = linkid;
624 	lva.lva_vh = vh;
625 	lva.lva_vnic[0] = '\0';
626 
627 	(void) dladm_walk_datalink_id(lookup_vnic, vh->vh_dh, &lva,
628 	    DATALINK_CLASS_VNIC, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
629 	if (strlen(lva.lva_vnic) != 0) {
630 		(void) strlcpy(vnic, lva.lva_vnic, len);
631 		return (VRRP_SUCCESS);
632 	}
633 
634 	return (VRRP_ENOVNIC);
635 }
636