xref: /illumos-gate/usr/src/cmd/cmd-inet/lib/ipmgmtd/ipmgmt_persist.c (revision f329b92316724de0d7ce227161bb130ec79d9eab)
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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 /*
27  * Contains DB walker functions, which are of type `db_wfunc_t';
28  *
29  * typedef boolean_t db_wfunc_t(void *cbarg, nvlist_t *db_nvl, char *buf,
30  *				size_t bufsize, int *errp);
31  *
32  * ipadm_rw_db() walks through the data store, one line at a time and calls
33  * these call back functions with:
34  *	`cbarg'  - callback argument
35  *	`db_nvl' - representing a line from DB in nvlist_t form
36  *	`buf'	 - character buffer to hold modified line
37  *	`bufsize'- size of the buffer
38  *	`errp' - captures any error inside the walker function.
39  *
40  * All the 'write' callback functions modify `db_nvl' based on `cbarg' and
41  * copy string representation of `db_nvl' (using ipadm_nvlist2str()) into `buf'.
42  * To delete a line from the DB, buf[0] is set to `\0'. Inside ipadm_rw_db(),
43  * the modified `buf' is written back into DB.
44  *
45  * All the 'read' callback functions, retrieve the information from the DB, by
46  * reading `db_nvl' and then populate the `cbarg'.
47  */
48 
49 #include <stdlib.h>
50 #include <strings.h>
51 #include <errno.h>
52 #include <assert.h>
53 #include <sys/types.h>
54 #include <sys/socket.h>
55 #include <netinet/in.h>
56 #include <arpa/inet.h>
57 #include <unistd.h>
58 #include "ipmgmt_impl.h"
59 
60 #define	ATYPE	"_atype"		/* name of the address type nvpair */
61 #define	FLAGS	"_flags"		/* name of the flags nvpair */
62 
63 /*
64  * flag used by ipmgmt_persist_aobjmap() to indicate address type is
65  * IPADM_ADDR_IPV6_ADDRCONF.
66  */
67 #define	IPMGMT_ATYPE_V6ACONF	0x1
68 
69 extern pthread_rwlock_t ipmgmt_dbconf_lock;
70 
71 /*
72  * Checks if the database nvl, `db_nvl' contains and matches ALL of the passed
73  * in private nvpairs `proto', `ifname' & `aobjname'.
74  */
75 static boolean_t
76 ipmgmt_nvlist_match(nvlist_t *db_nvl, const char *proto, const char *ifname,
77     const char *aobjname)
78 {
79 	char		*db_proto = NULL, *db_ifname = NULL;
80 	char		*db_aobjname = NULL;
81 	nvpair_t	*nvp;
82 	char		*name;
83 
84 	/* walk through db_nvl and retrieve all its private nvpairs */
85 	for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL;
86 	    nvp = nvlist_next_nvpair(db_nvl, nvp)) {
87 		name = nvpair_name(nvp);
88 		if (strcmp(IPADM_NVP_PROTONAME, name) == 0)
89 			(void) nvpair_value_string(nvp, &db_proto);
90 		else if (strcmp(IPADM_NVP_IFNAME, name) == 0)
91 			(void) nvpair_value_string(nvp, &db_ifname);
92 		else if (strcmp(IPADM_NVP_AOBJNAME, name) == 0)
93 			(void) nvpair_value_string(nvp, &db_aobjname);
94 	}
95 
96 	if (proto != NULL && proto[0] == '\0')
97 		proto = NULL;
98 	if (ifname != NULL && ifname[0] == '\0')
99 		ifname = NULL;
100 	if (aobjname != NULL && aobjname[0] == '\0')
101 		aobjname = NULL;
102 
103 	if ((proto == NULL && db_proto != NULL) ||
104 	    (proto != NULL && db_proto == NULL) ||
105 	    strcmp(proto, db_proto) != 0) {
106 		/* no intersection - different protocols. */
107 		return (B_FALSE);
108 	}
109 	if ((ifname == NULL && db_ifname != NULL) ||
110 	    (ifname != NULL && db_ifname == NULL) ||
111 	    strcmp(ifname, db_ifname) != 0) {
112 		/* no intersection - different interfaces. */
113 		return (B_FALSE);
114 	}
115 	if ((aobjname == NULL && db_aobjname != NULL) ||
116 	    (aobjname != NULL && db_aobjname == NULL) ||
117 	    strcmp(aobjname, db_aobjname) != 0) {
118 		/* no intersection - different address objects */
119 		return (B_FALSE);
120 	}
121 
122 	return (B_TRUE);
123 }
124 
125 /*
126  * Checks if the database nvl, `db_nvl' and the input nvl, `in_nvl' intersects.
127  */
128 static boolean_t
129 ipmgmt_nvlist_intersects(nvlist_t *db_nvl, nvlist_t *in_nvl)
130 {
131 	nvpair_t	*nvp;
132 	char		*name;
133 	char		*proto = NULL, *ifname = NULL, *aobjname = NULL;
134 
135 	/* walk through in_nvl and retrieve all its private nvpairs */
136 	for (nvp = nvlist_next_nvpair(in_nvl, NULL); nvp != NULL;
137 	    nvp = nvlist_next_nvpair(in_nvl, nvp)) {
138 		name = nvpair_name(nvp);
139 		if (strcmp(IPADM_NVP_PROTONAME, name) == 0)
140 			(void) nvpair_value_string(nvp, &proto);
141 		else if (strcmp(IPADM_NVP_IFNAME, name) == 0)
142 			(void) nvpair_value_string(nvp, &ifname);
143 		else if (strcmp(IPADM_NVP_AOBJNAME, name) == 0)
144 			(void) nvpair_value_string(nvp, &aobjname);
145 	}
146 
147 	return (ipmgmt_nvlist_match(db_nvl, proto, ifname, aobjname));
148 }
149 
150 /*
151  * Checks if the database nvl, `db_nvl', contains and matches ANY of the passed
152  * in private nvpairs `proto', `ifname' & `aobjname'.
153  */
154 static boolean_t
155 ipmgmt_nvlist_contains(nvlist_t *db_nvl, const char *proto,
156     const char *ifname, char *aobjname)
157 {
158 	char		*db_ifname = NULL, *db_proto = NULL;
159 	char		*db_aobjname = NULL;
160 	nvpair_t	*nvp;
161 	char		*name;
162 
163 	/* walk through db_nvl and retrieve all private nvpairs */
164 	for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL;
165 	    nvp = nvlist_next_nvpair(db_nvl, nvp)) {
166 		name = nvpair_name(nvp);
167 		if (strcmp(IPADM_NVP_PROTONAME, name) == 0)
168 			(void) nvpair_value_string(nvp, &db_proto);
169 		else if (strcmp(IPADM_NVP_IFNAME, name) == 0)
170 			(void) nvpair_value_string(nvp, &db_ifname);
171 		else if (strcmp(IPADM_NVP_AOBJNAME, name) == 0)
172 			(void) nvpair_value_string(nvp, &db_aobjname);
173 	}
174 
175 	if (proto != NULL && proto[0] != '\0') {
176 		if ((db_proto == NULL || strcmp(proto, db_proto) != 0))
177 			return (B_FALSE);
178 	}
179 	if (ifname != NULL && ifname[0] != '\0') {
180 		if ((db_ifname == NULL || strcmp(ifname, db_ifname) != 0))
181 			return (B_FALSE);
182 	}
183 	if (aobjname != NULL && aobjname[0] != '\0') {
184 		if ((db_aobjname == NULL || strcmp(aobjname, db_aobjname) != 0))
185 			return (B_FALSE);
186 	}
187 
188 	return (B_TRUE);
189 }
190 
191 /*
192  * Retrieves the property value from the DB. The property whose value is to be
193  * retrieved is in `pargp->ia_pname'.
194  */
195 /* ARGSUSED */
196 boolean_t
197 ipmgmt_db_getprop(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen,
198     int *errp)
199 {
200 	ipmgmt_prop_arg_t	*pargp = arg;
201 	boolean_t		cont = B_TRUE;
202 	char			*pval;
203 	int			err = 0;
204 
205 	*errp = 0;
206 
207 	if (!ipmgmt_nvlist_match(db_nvl, pargp->ia_module,
208 	    pargp->ia_ifname, pargp->ia_aobjname))
209 		return (B_TRUE);
210 
211 	if ((err = nvlist_lookup_string(db_nvl, pargp->ia_pname,
212 	    &pval)) == 0) {
213 		(void) strlcpy(pargp->ia_pval, pval, sizeof (pargp->ia_pval));
214 		/*
215 		 * We have retrieved what we are looking for.
216 		 * Stop the walker.
217 		 */
218 		cont = B_FALSE;
219 	} else {
220 		if (err == ENOENT)
221 			err = 0;
222 		*errp = err;
223 	}
224 
225 	return (cont);
226 }
227 
228 /*
229  * Removes the property value from the DB. The property whose value is to be
230  * removed is in `pargp->ia_pname'.
231  */
232 /* ARGSUSED */
233 boolean_t
234 ipmgmt_db_resetprop(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen,
235     int *errp)
236 {
237 	ipmgmt_prop_arg_t	*pargp = arg;
238 
239 	*errp = 0;
240 	if (!ipmgmt_nvlist_match(db_nvl, pargp->ia_module,
241 	    pargp->ia_ifname, pargp->ia_aobjname))
242 		return (B_TRUE);
243 
244 	if (!nvlist_exists(db_nvl, pargp->ia_pname))
245 		return (B_TRUE);
246 
247 	/*
248 	 * We found the property in the DB. If IPMGMT_REMOVE is not set then
249 	 * delete the entry from the db. If it is set, then the property is a
250 	 * multi-valued property so just remove the specified values from DB.
251 	 */
252 	if (pargp->ia_flags & IPMGMT_REMOVE) {
253 		char	*dbpval = NULL;
254 		char	*inpval = pargp->ia_pval;
255 		char	pval[MAXPROPVALLEN];
256 		char	*val, *lasts;
257 
258 		*errp = nvlist_lookup_string(db_nvl, pargp->ia_pname, &dbpval);
259 		if (*errp != 0)
260 			return (B_FALSE);
261 
262 		/*
263 		 * multi-valued properties are represented as comma separated
264 		 * values. Use string tokenizer functions to split them and
265 		 * search for the value to be removed.
266 		 */
267 		bzero(pval, sizeof (pval));
268 		if ((val = strtok_r(dbpval, ",", &lasts)) != NULL) {
269 			if (strcmp(val, inpval) != 0)
270 				(void) strlcat(pval, val, MAXPROPVALLEN);
271 			while ((val = strtok_r(NULL, ",", &lasts)) != NULL) {
272 				if (strcmp(val, inpval) != 0) {
273 					if (pval[0] != '\0')
274 						(void) strlcat(pval, ",",
275 						    MAXPROPVALLEN);
276 					(void) strlcat(pval, val,
277 					    MAXPROPVALLEN);
278 				}
279 			}
280 		} else {
281 			if (strcmp(dbpval, inpval) != 0)
282 				*errp = ENOENT;
283 			else
284 				buf[0] =  '\0';
285 			return (B_FALSE);
286 		}
287 		*errp = nvlist_add_string(db_nvl, pargp->ia_pname, pval);
288 		if (*errp != 0)
289 			return (B_FALSE);
290 
291 		(void) memset(buf, 0, buflen);
292 		if (ipadm_nvlist2str(db_nvl, buf, buflen) == 0) {
293 			/* buffer overflow */
294 			*errp = ENOBUFS;
295 		}
296 	} else {
297 		buf[0] = '\0';
298 	}
299 
300 	/* stop the search */
301 	return (B_FALSE);
302 }
303 
304 /*
305  * Input arguments can have IPADM_NVP_AOBJNAME or IPADM_NVP_IFNAME. A match is
306  * found, when one of the following occurs first.
307  * - the input aobjname matches the db aobjname. Return the db address.
308  * - the input interface matches the db interface. Return all the
309  *   matching db lines with addresses.
310  */
311 /* ARGSUSED */
312 boolean_t
313 ipmgmt_db_getaddr(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen,
314     int *errp)
315 {
316 	ipmgmt_getaddr_cbarg_t	*cbarg = arg;
317 	char		*db_aobjname = NULL;
318 	char		*db_ifname = NULL;
319 	nvlist_t	*db_addr = NULL;
320 	char		name[IPMGMT_STRSIZE];
321 	nvpair_t	*nvp;
322 	boolean_t	add_nvl = B_FALSE;
323 
324 	/* Parse db nvlist */
325 	for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL;
326 	    nvp = nvlist_next_nvpair(db_nvl, nvp)) {
327 		if (nvpair_type(nvp) == DATA_TYPE_NVLIST)
328 			(void) nvpair_value_nvlist(nvp, &db_addr);
329 		else if (strcmp(nvpair_name(nvp), IPADM_NVP_IFNAME) == 0)
330 			(void) nvpair_value_string(nvp, &db_ifname);
331 		else if (strcmp(nvpair_name(nvp), IPADM_NVP_AOBJNAME) == 0)
332 			(void) nvpair_value_string(nvp, &db_aobjname);
333 	}
334 
335 	if (db_aobjname == NULL) /* Not an address */
336 		return (B_TRUE);
337 
338 	/* Check for a match between the aobjnames or the interface name */
339 	if (cbarg->cb_aobjname[0] != '\0') {
340 		if (strcmp(cbarg->cb_aobjname, db_aobjname) == 0)
341 			add_nvl = B_TRUE;
342 	} else if (cbarg->cb_ifname[0] != '\0') {
343 		if (strcmp(cbarg->cb_ifname, db_ifname) == 0)
344 			add_nvl = B_TRUE;
345 	} else {
346 		add_nvl = B_TRUE;
347 	}
348 
349 	if (add_nvl) {
350 		(void) snprintf(name, sizeof (name), "%s_%d", db_ifname,
351 		    cbarg->cb_ocnt);
352 		*errp = nvlist_add_nvlist(cbarg->cb_onvl, name, db_nvl);
353 		if (*errp == 0)
354 			cbarg->cb_ocnt++;
355 	}
356 	return (B_TRUE);
357 }
358 
359 /*
360  * This function takes the appropriate lock, read or write, based on the
361  * `db_op' and then calls DB walker ipadm_rw_db().
362  */
363 extern int
364 ipmgmt_db_walk(db_wfunc_t *db_walk_func, void *db_warg, ipadm_db_op_t db_op)
365 {
366 	int		err;
367 	boolean_t	writeop;
368 	mode_t		mode;
369 
370 	writeop = (db_op != IPADM_DB_READ);
371 
372 	if (writeop) {
373 		(void) pthread_rwlock_wrlock(&ipmgmt_dbconf_lock);
374 		mode = IPADM_FILE_MODE;
375 	} else {
376 		(void) pthread_rwlock_rdlock(&ipmgmt_dbconf_lock);
377 		mode = 0;
378 	}
379 
380 	err = ipadm_rw_db(db_walk_func, db_warg, IPADM_DB_FILE, mode, db_op);
381 	(void) pthread_rwlock_unlock(&ipmgmt_dbconf_lock);
382 	return (err);
383 }
384 
385 /*
386  * Used to add an entry towards the end of DB. It just returns B_TRUE for
387  * every line of the DB. When we reach the end, ipadm_rw_db() adds the
388  * line at the end.
389  */
390 /* ARGSUSED */
391 boolean_t
392 ipmgmt_db_add(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp)
393 {
394 	return (B_TRUE);
395 }
396 
397 /*
398  * This function is used to update or create an entry in DB. The nvlist_t,
399  * `in_nvl', represents the line we are looking for. Once we ensure the right
400  * line from DB, we update that entry.
401  */
402 boolean_t
403 ipmgmt_db_update(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen,
404     int *errp)
405 {
406 	ipadm_dbwrite_cbarg_t	*cb = arg;
407 	uint_t			flags = cb->dbw_flags;
408 	nvlist_t		*in_nvl = cb->dbw_nvl;
409 	nvpair_t		*nvp;
410 	char			*name, *instrval = NULL, *dbstrval = NULL;
411 	char			pval[MAXPROPVALLEN];
412 
413 	if (!ipmgmt_nvlist_intersects(db_nvl, in_nvl))
414 		return (B_TRUE);
415 
416 	for (nvp = nvlist_next_nvpair(in_nvl, NULL); nvp != NULL;
417 	    nvp = nvlist_next_nvpair(in_nvl, nvp)) {
418 		name = nvpair_name(nvp);
419 		if (!IPADM_PRIV_NVP(name) && nvlist_exists(db_nvl, name))
420 			break;
421 	}
422 
423 	if (nvp == NULL)
424 		return (B_TRUE);
425 
426 	assert(nvpair_type(nvp) == DATA_TYPE_STRING);
427 
428 	if ((*errp = nvpair_value_string(nvp, &instrval)) != 0)
429 		return (B_FALSE);
430 
431 	/*
432 	 * If IPMGMT_APPEND is set then we are dealing with multi-valued
433 	 * properties. We append to the entry from the db, with the new value.
434 	 */
435 	if (flags & IPMGMT_APPEND) {
436 		if ((*errp = nvlist_lookup_string(db_nvl, name,
437 		    &dbstrval)) != 0)
438 			return (B_FALSE);
439 		(void) snprintf(pval, MAXPROPVALLEN, "%s,%s", dbstrval,
440 		    instrval);
441 		if ((*errp = nvlist_add_string(db_nvl, name, pval)) != 0)
442 			return (B_FALSE);
443 	} else {
444 		/* case	of in-line update of a db entry */
445 		if ((*errp = nvlist_add_string(db_nvl, name, instrval)) != 0)
446 			return (B_FALSE);
447 	}
448 
449 	(void) memset(buf, 0, buflen);
450 	if (ipadm_nvlist2str(db_nvl, buf, buflen) == 0) {
451 		/* buffer overflow */
452 		*errp = ENOBUFS;
453 	}
454 	*errp = 0;
455 
456 	/* we updated the DB entry, so do not continue */
457 	return (B_FALSE);
458 }
459 
460 /*
461  * For the given `cbarg->cb_ifname' interface, retrieves any persistent
462  * interface information (used in 'ipadm show-if')
463  */
464 /* ARGSUSED */
465 boolean_t
466 ipmgmt_db_getif(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen,
467     int *errp)
468 {
469 	ipmgmt_getif_cbarg_t	*cbarg = arg;
470 	char			*ifname = cbarg->cb_ifname;
471 	char			*intf = NULL;
472 	ipadm_if_info_t		*ifp = NULL;
473 	sa_family_t		af;
474 	char			*afstr;
475 
476 	*errp = 0;
477 	if (nvlist_lookup_string(db_nvl, IPADM_NVP_FAMILY, &afstr) != 0 ||
478 	    nvlist_lookup_string(db_nvl, IPADM_NVP_IFNAME, &intf) != 0 ||
479 	    (ifname[0] != '\0' && strcmp(ifname, intf) != 0)) {
480 		return (B_TRUE);
481 	}
482 	af = atoi(afstr);
483 	for (ifp = cbarg->cb_ifinfo; ifp != NULL; ifp = ifp->ifi_next) {
484 		if (strcmp(ifp->ifi_name, intf) == 0)
485 			break;
486 	}
487 	if (ifp == NULL) {
488 		ipadm_if_info_t *new;
489 
490 		if ((new = calloc(1, sizeof (*new))) == NULL) {
491 			*errp = ENOMEM;
492 			return (B_FALSE); /* don't continue the walk */
493 		}
494 		new->ifi_next = cbarg->cb_ifinfo;
495 		cbarg->cb_ifinfo = new;
496 		ifp = new;
497 		(void) strlcpy(ifp->ifi_name, intf, sizeof (ifp->ifi_name));
498 	}
499 
500 	if (af == AF_INET) {
501 		ifp->ifi_pflags |= IFIF_IPV4;
502 	} else {
503 		assert(af == AF_INET6);
504 		ifp->ifi_pflags |= IFIF_IPV6;
505 	}
506 
507 	/* Terminate the walk if we found both v4 and v6 interfaces. */
508 	if (ifname[0] != '\0' && (ifp->ifi_pflags & IFIF_IPV4) &&
509 	    (ifp->ifi_pflags & IFIF_IPV6))
510 		return (B_FALSE);
511 
512 	return (B_TRUE);
513 }
514 
515 /*
516  * Deletes those entries from the database for which interface name
517  * matches with the given `cbarg->cb_ifname'
518  */
519 /* ARGSUSED */
520 boolean_t
521 ipmgmt_db_resetif(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen,
522     int *errp)
523 {
524 	ipmgmt_if_cbarg_t *cbarg = arg;
525 	boolean_t	isv6 = (cbarg->cb_family == AF_INET6);
526 	char		*ifname = cbarg->cb_ifname;
527 	char		*modstr = NULL;
528 	char		*afstr;
529 	char		*aobjname;
530 	uint_t		proto;
531 	ipmgmt_aobjmap_t *head;
532 	boolean_t	aobjfound = B_FALSE;
533 
534 	*errp = 0;
535 
536 	if (!ipmgmt_nvlist_contains(db_nvl, NULL, ifname, NULL))
537 		return (B_TRUE);
538 
539 	if (nvlist_lookup_string(db_nvl, IPADM_NVP_FAMILY, &afstr) == 0) {
540 		if (atoi(afstr) == cbarg->cb_family)
541 			goto delete;
542 		return (B_TRUE);
543 	}
544 
545 	/* Reset all the interface configurations for 'ifname' */
546 	if (isv6 && (nvlist_exists(db_nvl, IPADM_NVP_IPV6ADDR) ||
547 	    nvlist_exists(db_nvl, IPADM_NVP_INTFID))) {
548 		goto delete;
549 	}
550 	if (!isv6 &&
551 	    (nvlist_exists(db_nvl, IPADM_NVP_IPV4ADDR) ||
552 	    nvlist_exists(db_nvl, IPADM_NVP_DHCP))) {
553 		goto delete;
554 	}
555 
556 	if (nvlist_lookup_string(db_nvl, IPADM_NVP_AOBJNAME, &aobjname) == 0) {
557 		/*
558 		 * This must be an address property. Delete this
559 		 * line if there is a match in the address family.
560 		 */
561 		head = aobjmap.aobjmap_head;
562 		while (head != NULL) {
563 			if (strcmp(head->am_aobjname, aobjname) == 0) {
564 				aobjfound = B_TRUE;
565 				if (head->am_family == cbarg->cb_family)
566 					goto delete;
567 			}
568 			head = head->am_next;
569 		}
570 		/*
571 		 * If aobjfound = B_FALSE, then this address is not
572 		 * available in active configuration. We should go ahead
573 		 * and delete it.
574 		 */
575 		if (!aobjfound)
576 			goto delete;
577 	}
578 
579 	/*
580 	 * If we are removing both v4 and v6 interface, then we get rid of
581 	 * all the properties for that interface. On the other hand, if we
582 	 * are deleting only v4 instance of an interface, then we delete v4
583 	 * properties only.
584 	 */
585 	if (nvlist_lookup_string(db_nvl, IPADM_NVP_PROTONAME, &modstr) == 0) {
586 		proto = ipadm_str2proto(modstr);
587 		switch (proto) {
588 		case MOD_PROTO_IPV6:
589 			if (isv6)
590 				goto delete;
591 			break;
592 		case MOD_PROTO_IPV4:
593 			if (!isv6)
594 				goto delete;
595 			break;
596 		case MOD_PROTO_IP:
597 			/* this should never be the case, today */
598 			assert(0);
599 			break;
600 		}
601 	}
602 	/* Not found a match yet. Continue processing the db */
603 	return (B_TRUE);
604 delete:
605 	/* delete the line from the db */
606 	buf[0] = '\0';
607 	return (B_TRUE);
608 }
609 
610 /*
611  * Deletes those entries from the database for which address object name
612  * matches with the given `cbarg->cb_aobjname'
613  */
614 /* ARGSUSED */
615 boolean_t
616 ipmgmt_db_resetaddr(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen,
617     int *errp)
618 {
619 	ipmgmt_resetaddr_cbarg_t *cbarg = arg;
620 	char		*aobjname = cbarg->cb_aobjname;
621 
622 	*errp = 0;
623 	if (!ipmgmt_nvlist_contains(db_nvl, NULL, NULL, aobjname))
624 		return (B_TRUE);
625 
626 	/* delete the line from the db */
627 	buf[0] = '\0';
628 	return (B_TRUE);
629 }
630 
631 /*
632  * Retrieves all interface props, including addresses, for given interface(s).
633  * `invl' contains the list of interfaces, for which information need to be
634  * retrieved.
635  */
636 /* ARGSUSED */
637 boolean_t
638 ipmgmt_db_initif(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen,
639     int *errp)
640 {
641 	ipmgmt_initif_cbarg_t	*cbarg = arg;
642 	nvlist_t		*onvl = cbarg->cb_onvl;
643 	nvlist_t		*invl = cbarg->cb_invl;
644 	sa_family_t		in_af = cbarg->cb_family;
645 	char			*db_ifname;
646 
647 	*errp = 0;
648 	if (nvlist_lookup_string(db_nvl, IPADM_NVP_IFNAME, &db_ifname) == 0 &&
649 	    nvlist_exists(invl, db_ifname)) {
650 		char		name[IPMGMT_STRSIZE];
651 		sa_family_t	db_af = in_af;
652 		uint_t		proto;
653 		char		*pstr;
654 
655 		if (in_af != AF_UNSPEC) {
656 			if (nvlist_lookup_string(db_nvl, IPADM_NVP_PROTONAME,
657 			    &pstr) == 0) {
658 				proto = ipadm_str2proto(pstr);
659 				if (proto == MOD_PROTO_IPV4)
660 					db_af = AF_INET;
661 				else if (proto == MOD_PROTO_IPV6)
662 					db_af = AF_INET6;
663 				else
664 					db_af = in_af;
665 			} else {
666 				if (nvlist_exists(db_nvl, IPADM_NVP_IPV4ADDR) ||
667 				    nvlist_exists(db_nvl, IPADM_NVP_DHCP))
668 					db_af = AF_INET;
669 				else
670 					db_af = AF_INET6;
671 			}
672 		}
673 		if (in_af == db_af) {
674 			(void) snprintf(name, sizeof (name), "%s_%d", db_ifname,
675 			    cbarg->cb_ocnt);
676 			*errp = nvlist_add_nvlist(onvl, name, db_nvl);
677 			if (*errp == 0)
678 				cbarg->cb_ocnt++;
679 		}
680 	}
681 	return (B_TRUE);
682 }
683 
684 /*
685  * helper function for ipmgmt_aobjmap_op(). Adds the node pointed by `nodep'
686  * into `aobjmap' structure.
687  */
688 static int
689 i_ipmgmt_add_amnode(ipmgmt_aobjmap_t *nodep)
690 {
691 	ipmgmt_aobjmap_t	*new, *head;
692 
693 	head = aobjmap.aobjmap_head;
694 	if ((new = malloc(sizeof (ipmgmt_aobjmap_t))) == NULL)
695 		return (ENOMEM);
696 	*new = *nodep;
697 	new->am_next = NULL;
698 
699 	/* Add the node at the beginning of the list */
700 	if (head == NULL) {
701 		aobjmap.aobjmap_head = new;
702 	} else {
703 		new->am_next = aobjmap.aobjmap_head;
704 		aobjmap.aobjmap_head = new;
705 	}
706 	return (0);
707 }
708 
709 /*
710  * A recursive function to generate alphabetized number given a decimal number.
711  * Decimal 0 to 25 maps to 'a' to 'z' and then the counting continues with 'aa',
712  * 'ab', 'ac', et al.
713  */
714 static void
715 i_ipmgmt_num2priv_aobjname(uint32_t num, char **cp, char *endp)
716 {
717 	if (num >= 26)
718 		i_ipmgmt_num2priv_aobjname(num / 26 - 1, cp, endp);
719 	if (*cp != endp) {
720 		*cp[0] = 'a' + (num % 26);
721 		(*cp)++;
722 	}
723 }
724 
725 /*
726  * This function generates an `aobjname', when required, and then does
727  * lookup-add. If `nodep->am_aobjname' is not an empty string, then it walks
728  * through the `aobjmap' to check if an address object with the same
729  * `nodep->am_aobjname' exists. If it exists, EEXIST is returned as duplicate
730  * `aobjname's are not allowed.
731  *
732  * If `nodep->am_aobjname' is an empty string then the daemon generates an
733  * `aobjname' using the `am_nextnum', which contains the next number to be
734  * used to generate `aobjname'. `am_nextnum' is converted to base26 using
735  * `a-z' alphabets in i_ipmgmt_num2priv_aobjname().
736  *
737  * `am_nextnum' will be 0 to begin with. Every time an address object that
738  * needs `aobjname' is added it's incremented by 1. So for the first address
739  * object on net0 the `am_aobjname' will be net0/_a and `am_nextnum' will be 1.
740  * For the second address object on that interface `am_aobjname' will be net0/_b
741  * and  `am_nextnum' will incremented to 2.
742  */
743 static int
744 i_ipmgmt_lookupadd_amnode(ipmgmt_aobjmap_t *nodep)
745 {
746 	ipmgmt_aobjmap_t	*head;
747 	uint32_t		nextnum;
748 
749 	for (head = aobjmap.aobjmap_head; head != NULL; head = head->am_next)
750 		if (strcmp(head->am_ifname, nodep->am_ifname) == 0)
751 			break;
752 	nextnum = (head == NULL ? 0 : head->am_nextnum);
753 
754 	/*
755 	 * if `aobjname' is empty, then the daemon has to generate the
756 	 * next `aobjname' for the given interface and family.
757 	 */
758 	if (nodep->am_aobjname[0] == '\0') {
759 		char tmpstr[IPADM_AOBJ_USTRSIZ - 1];  /* 1 for leading  '_' */
760 		char *cp = tmpstr;
761 		char *endp = tmpstr + sizeof (tmpstr);
762 
763 		i_ipmgmt_num2priv_aobjname(nextnum, &cp, endp);
764 
765 		if (cp == endp)
766 			return (EINVAL);
767 		cp[0] = '\0';
768 
769 		if (snprintf(nodep->am_aobjname, IPADM_AOBJSIZ, "%s/_%s",
770 		    nodep->am_ifname, tmpstr) >= IPADM_AOBJSIZ) {
771 			return (EINVAL);
772 		}
773 		nodep->am_nextnum = ++nextnum;
774 	} else {
775 		for (head = aobjmap.aobjmap_head; head != NULL;
776 		    head = head->am_next) {
777 			if (strcmp(head->am_aobjname, nodep->am_aobjname) == 0)
778 				return (EEXIST);
779 		}
780 		nodep->am_nextnum = nextnum;
781 	}
782 	return (i_ipmgmt_add_amnode(nodep));
783 }
784 
785 /*
786  * Performs following operations on the global `aobjmap' linked list.
787  * (a) ADDROBJ_ADD: add or update address object in `aobjmap'
788  * (b) ADDROBJ_DELETE: delete address object from `aobjmap'
789  * (c) ADDROBJ_LOOKUPADD: place a stub address object in `aobjmap'
790  * (d) ADDROBJ_SETLIFNUM: Sets the lifnum for an address object in `aobjmap'
791  */
792 int
793 ipmgmt_aobjmap_op(ipmgmt_aobjmap_t *nodep, uint32_t op)
794 {
795 	ipmgmt_aobjmap_t	*head, *prev, *matched = NULL;
796 	boolean_t		update = B_TRUE;
797 	int			err = 0;
798 	ipadm_db_op_t		db_op;
799 
800 	(void) pthread_rwlock_wrlock(&aobjmap.aobjmap_rwlock);
801 
802 	head = aobjmap.aobjmap_head;
803 	switch (op) {
804 	case ADDROBJ_ADD:
805 		/*
806 		 * check for stub nodes (added by ADDROBJ_LOOKUPADD) and
807 		 * update, else add the new node.
808 		 */
809 		for (; head != NULL; head = head->am_next) {
810 			/*
811 			 * For IPv6, we need to distinguish between the
812 			 * linklocal and non-linklocal nodes
813 			 */
814 			if (strcmp(head->am_aobjname,
815 			    nodep->am_aobjname) == 0 &&
816 			    (head->am_atype != IPADM_ADDR_IPV6_ADDRCONF ||
817 			    head->am_linklocal == nodep->am_linklocal))
818 				break;
819 		}
820 
821 		if (head != NULL) {
822 			/* update the node */
823 			(void) strlcpy(head->am_ifname, nodep->am_ifname,
824 			    sizeof (head->am_ifname));
825 			head->am_lnum = nodep->am_lnum;
826 			head->am_family = nodep->am_family;
827 			head->am_flags = nodep->am_flags;
828 			head->am_atype = nodep->am_atype;
829 			if (head->am_atype == IPADM_ADDR_IPV6_ADDRCONF) {
830 				head->am_ifid = nodep->am_ifid;
831 				head->am_linklocal = nodep->am_linklocal;
832 			}
833 		} else {
834 			for (head = aobjmap.aobjmap_head; head != NULL;
835 			    head = head->am_next) {
836 				if (strcmp(head->am_ifname,
837 				    nodep->am_ifname) == 0)
838 					break;
839 			}
840 			nodep->am_nextnum = (head == NULL ? 0 :
841 			    head->am_nextnum);
842 			err = i_ipmgmt_add_amnode(nodep);
843 		}
844 		db_op = IPADM_DB_WRITE;
845 		break;
846 	case ADDROBJ_DELETE:
847 		prev = head;
848 		while (head != NULL) {
849 			if (strcmp(head->am_aobjname,
850 			    nodep->am_aobjname) == 0) {
851 				nodep->am_atype = head->am_atype;
852 				/*
853 				 * There could be multiple IPV6_ADDRCONF nodes,
854 				 * with same address object name, so check for
855 				 * logical number also.
856 				 */
857 				if (head->am_atype !=
858 				    IPADM_ADDR_IPV6_ADDRCONF ||
859 				    nodep->am_lnum == head->am_lnum)
860 					break;
861 			}
862 			prev = head;
863 			head = head->am_next;
864 		}
865 		if (head != NULL) {
866 			/*
867 			 * If the address object is in both active and
868 			 * persistent configuration and the user is deleting it
869 			 * only from active configuration then mark this node
870 			 * for deletion by reseting IPMGMT_ACTIVE bit.
871 			 * With this the same address object name cannot
872 			 * be reused until it is permanently removed.
873 			 */
874 			if (head->am_flags == (IPMGMT_ACTIVE|IPMGMT_PERSIST) &&
875 			    nodep->am_flags == IPMGMT_ACTIVE) {
876 				/* Update flags in the in-memory map. */
877 				head->am_flags &= ~IPMGMT_ACTIVE;
878 				head->am_lnum = -1;
879 
880 				/* Update info in file. */
881 				db_op = IPADM_DB_WRITE;
882 				*nodep = *head;
883 			} else {
884 				(void) strlcpy(nodep->am_ifname,
885 				    head->am_ifname,
886 				    sizeof (nodep->am_ifname));
887 				/* otherwise delete the node */
888 				if (head == aobjmap.aobjmap_head)
889 					aobjmap.aobjmap_head = head->am_next;
890 				else
891 					prev->am_next = head->am_next;
892 				free(head);
893 				db_op = IPADM_DB_DELETE;
894 			}
895 		} else {
896 			err = ENOENT;
897 		}
898 		break;
899 	case ADDROBJ_LOOKUPADD:
900 		err = i_ipmgmt_lookupadd_amnode(nodep);
901 		update = B_FALSE;
902 		break;
903 	case ADDROBJ_SETLIFNUM:
904 		update = B_FALSE;
905 		for (; head != NULL; head = head->am_next) {
906 			if (strcmp(head->am_ifname,
907 			    nodep->am_ifname) == 0 &&
908 			    head->am_family == nodep->am_family &&
909 			    head->am_lnum == nodep->am_lnum) {
910 				err = EEXIST;
911 				break;
912 			}
913 			if (strcmp(head->am_aobjname,
914 			    nodep->am_aobjname) == 0) {
915 				matched = head;
916 			}
917 		}
918 		if (err == EEXIST)
919 			break;
920 		if (matched != NULL) {
921 			/* update the lifnum */
922 			matched->am_lnum = nodep->am_lnum;
923 		} else {
924 			err = ENOENT;
925 		}
926 		break;
927 	default:
928 		assert(0);
929 	}
930 
931 	if (err == 0 && update)
932 		err = ipmgmt_persist_aobjmap(nodep, db_op);
933 
934 	(void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock);
935 
936 	return (err);
937 }
938 
939 /*
940  * Given a node in `aobjmap', this function converts it into nvlist_t structure.
941  * The content to be written to DB must be represented as nvlist_t.
942  */
943 static int
944 i_ipmgmt_node2nvl(nvlist_t **nvl, ipmgmt_aobjmap_t *np)
945 {
946 	int	err;
947 	char	strval[IPMGMT_STRSIZE];
948 
949 	*nvl = NULL;
950 	if ((err = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0)) != 0)
951 		goto fail;
952 
953 	if ((err = nvlist_add_string(*nvl, IPADM_NVP_AOBJNAME,
954 	    np->am_aobjname)) != 0)
955 		goto fail;
956 
957 	if ((err = nvlist_add_string(*nvl, IPADM_NVP_IFNAME,
958 	    np->am_ifname)) != 0)
959 		goto fail;
960 
961 	(void) snprintf(strval, IPMGMT_STRSIZE, "%d", np->am_lnum);
962 	if ((err = nvlist_add_string(*nvl, IPADM_NVP_LIFNUM, strval)) != 0)
963 		goto fail;
964 
965 	(void) snprintf(strval, IPMGMT_STRSIZE, "%d", np->am_family);
966 	if ((err = nvlist_add_string(*nvl, IPADM_NVP_FAMILY, strval)) != 0)
967 		goto fail;
968 
969 	(void) snprintf(strval, IPMGMT_STRSIZE, "%d", np->am_flags);
970 	if ((err = nvlist_add_string(*nvl, FLAGS, strval)) != 0)
971 		goto fail;
972 
973 	(void) snprintf(strval, IPMGMT_STRSIZE, "%d", np->am_atype);
974 	if ((err = nvlist_add_string(*nvl, ATYPE, strval)) != 0)
975 		goto fail;
976 
977 	if (np->am_atype == IPADM_ADDR_IPV6_ADDRCONF) {
978 		struct sockaddr_in6	*in6;
979 
980 		in6 = (struct sockaddr_in6 *)&np->am_ifid;
981 		if (np->am_linklocal &&
982 		    IN6_IS_ADDR_UNSPECIFIED(&in6->sin6_addr)) {
983 			if ((err = nvlist_add_string(*nvl, IPADM_NVP_IPNUMADDR,
984 			    "default")) != 0)
985 				goto fail;
986 		} else {
987 			if (inet_ntop(AF_INET6, &in6->sin6_addr, strval,
988 			    IPMGMT_STRSIZE) == NULL) {
989 				err = errno;
990 				goto fail;
991 			}
992 			if ((err = nvlist_add_string(*nvl, IPADM_NVP_IPNUMADDR,
993 			    strval)) != 0)
994 				goto fail;
995 		}
996 	} else {
997 		if ((err = nvlist_add_string(*nvl, IPADM_NVP_IPNUMADDR,
998 		    "")) != 0)
999 			goto fail;
1000 	}
1001 	return (err);
1002 fail:
1003 	nvlist_free(*nvl);
1004 	return (err);
1005 }
1006 
1007 /*
1008  * Read the aobjmap data store and build the in-memory representation
1009  * of the aobjmap. We don't need to hold any locks while building this as
1010  * we do this in very early stage of daemon coming up, even before the door
1011  * is opened.
1012  */
1013 /* ARGSUSED */
1014 extern boolean_t
1015 ipmgmt_aobjmap_init(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen,
1016     int *errp)
1017 {
1018 	nvpair_t		*nvp = NULL;
1019 	char			*name, *strval = NULL;
1020 	ipmgmt_aobjmap_t 	node;
1021 	struct sockaddr_in6	*in6;
1022 
1023 	*errp = 0;
1024 	node.am_next = NULL;
1025 	for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL;
1026 	    nvp = nvlist_next_nvpair(db_nvl, nvp)) {
1027 		name = nvpair_name(nvp);
1028 
1029 		if ((*errp = nvpair_value_string(nvp, &strval)) != 0)
1030 			return (B_TRUE);
1031 		if (strcmp(IPADM_NVP_AOBJNAME, name) == 0) {
1032 			(void) strlcpy(node.am_aobjname, strval,
1033 			    sizeof (node.am_aobjname));
1034 		} else if (strcmp(IPADM_NVP_IFNAME, name) == 0) {
1035 			(void) strlcpy(node.am_ifname, strval,
1036 			    sizeof (node.am_ifname));
1037 		} else if (strcmp(IPADM_NVP_LIFNUM, name) == 0) {
1038 			node.am_lnum = atoi(strval);
1039 		} else if (strcmp(IPADM_NVP_FAMILY, name) == 0) {
1040 			node.am_family = (sa_family_t)atoi(strval);
1041 		} else if (strcmp(FLAGS, name) == 0) {
1042 			node.am_flags = atoi(strval);
1043 		} else if (strcmp(ATYPE, name) == 0) {
1044 			node.am_atype = (ipadm_addr_type_t)atoi(strval);
1045 		} else if (strcmp(IPADM_NVP_IPNUMADDR, name) == 0) {
1046 			if (node.am_atype == IPADM_ADDR_IPV6_ADDRCONF) {
1047 				in6 = (struct sockaddr_in6 *)&node.am_ifid;
1048 				if (strcmp(strval, "default") == 0) {
1049 					bzero(in6, sizeof (node.am_ifid));
1050 					node.am_linklocal = B_TRUE;
1051 				} else {
1052 					(void) inet_pton(AF_INET6, strval,
1053 					    &in6->sin6_addr);
1054 					if (IN6_IS_ADDR_UNSPECIFIED(
1055 					    &in6->sin6_addr))
1056 						node.am_linklocal = B_TRUE;
1057 				}
1058 			}
1059 		}
1060 	}
1061 
1062 	/* we have all the information we need, add the node */
1063 	*errp = i_ipmgmt_add_amnode(&node);
1064 
1065 	return (B_TRUE);
1066 }
1067 
1068 /*
1069  * Updates an entry from the temporary cache file, which matches the given
1070  * address object name.
1071  */
1072 /* ARGSUSED */
1073 static boolean_t
1074 ipmgmt_update_aobjmap(void *arg, nvlist_t *db_nvl, char *buf,
1075     size_t buflen, int *errp)
1076 {
1077 	ipadm_dbwrite_cbarg_t	*cb = arg;
1078 	nvlist_t		*in_nvl = cb->dbw_nvl;
1079 	uint32_t		flags = cb->dbw_flags;
1080 	char			*db_lifnumstr = NULL, *in_lifnumstr = NULL;
1081 
1082 	*errp = 0;
1083 	if (!ipmgmt_nvlist_intersects(db_nvl, in_nvl))
1084 		return (B_TRUE);
1085 
1086 	if (flags & IPMGMT_ATYPE_V6ACONF) {
1087 		if (nvlist_lookup_string(db_nvl, IPADM_NVP_LIFNUM,
1088 		    &db_lifnumstr) != 0 ||
1089 		    nvlist_lookup_string(in_nvl, IPADM_NVP_LIFNUM,
1090 		    &in_lifnumstr) != 0 ||
1091 		    (atoi(db_lifnumstr) != -1 && atoi(in_lifnumstr) != -1 &&
1092 		    strcmp(db_lifnumstr, in_lifnumstr) != 0))
1093 			return (B_TRUE);
1094 	}
1095 
1096 	/* we found the match */
1097 	(void) memset(buf, 0, buflen);
1098 	if (ipadm_nvlist2str(in_nvl, buf, buflen) == 0) {
1099 		/* buffer overflow */
1100 		*errp = ENOBUFS;
1101 	}
1102 
1103 	/* stop the walker */
1104 	return (B_FALSE);
1105 }
1106 
1107 /*
1108  * Deletes an entry from the temporary cache file, which matches the given
1109  * address object name.
1110  */
1111 /* ARGSUSED */
1112 static boolean_t
1113 ipmgmt_delete_aobjmap(void *arg, nvlist_t *db_nvl, char *buf,
1114     size_t buflen, int *errp)
1115 {
1116 	ipmgmt_aobjmap_t	*nodep = arg;
1117 	char			*db_lifnumstr = NULL;
1118 
1119 	*errp = 0;
1120 	if (!ipmgmt_nvlist_match(db_nvl, NULL, nodep->am_ifname,
1121 	    nodep->am_aobjname))
1122 		return (B_TRUE);
1123 
1124 	if (nodep->am_atype == IPADM_ADDR_IPV6_ADDRCONF) {
1125 		if (nvlist_lookup_string(db_nvl, IPADM_NVP_LIFNUM,
1126 		    &db_lifnumstr) != 0 || atoi(db_lifnumstr) != nodep->am_lnum)
1127 			return (B_TRUE);
1128 	}
1129 
1130 	/* we found the match, delete the line from the db */
1131 	buf[0] = '\0';
1132 
1133 	/* stop the walker */
1134 	return (B_FALSE);
1135 }
1136 
1137 /*
1138  * Adds or deletes aobjmap node information into a temporary cache file.
1139  */
1140 extern int
1141 ipmgmt_persist_aobjmap(ipmgmt_aobjmap_t *nodep, ipadm_db_op_t op)
1142 {
1143 	int			err;
1144 	ipadm_dbwrite_cbarg_t	cb;
1145 	nvlist_t		*nvl = NULL;
1146 
1147 	if (op == IPADM_DB_WRITE) {
1148 		if ((err = i_ipmgmt_node2nvl(&nvl, nodep)) != 0)
1149 			return (err);
1150 		cb.dbw_nvl = nvl;
1151 		if (nodep->am_atype == IPADM_ADDR_IPV6_ADDRCONF)
1152 			cb.dbw_flags = IPMGMT_ATYPE_V6ACONF;
1153 		else
1154 			cb.dbw_flags = 0;
1155 
1156 		err = ipadm_rw_db(ipmgmt_update_aobjmap, &cb,
1157 		    ADDROBJ_MAPPING_DB_FILE, IPADM_FILE_MODE, IPADM_DB_WRITE);
1158 		nvlist_free(nvl);
1159 	} else {
1160 		assert(op == IPADM_DB_DELETE);
1161 
1162 		err = ipadm_rw_db(ipmgmt_delete_aobjmap, nodep,
1163 		    ADDROBJ_MAPPING_DB_FILE, IPADM_FILE_MODE, IPADM_DB_DELETE);
1164 	}
1165 	return (err);
1166 }
1167