xref: /openbsd-src/usr.sbin/vmd/priv.c (revision 4e55220c92ad2b00df4c44125391f54be0e401da)
1 /*	$OpenBSD: priv.c,v 1.27 2024/11/24 10:44:59 kn Exp $	*/
2 
3 /*
4  * Copyright (c) 2016 Reyk Floeter <reyk@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #include <sys/ioctl.h>
22 
23 #include <net/if.h>
24 #include <netinet/in.h>
25 #include <netinet/if_ether.h>
26 #include <netinet6/in6_var.h>
27 #include <netinet6/nd6.h>
28 #include <net/if_bridge.h>
29 
30 #include <arpa/inet.h>
31 
32 #include <errno.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <ctype.h>
38 
39 #include "proc.h"
40 #include "vmd.h"
41 
42 int	 priv_dispatch_parent(int, struct privsep_proc *, struct imsg *);
43 void	 priv_run(struct privsep *, struct privsep_proc *, void *);
44 
45 static struct privsep_proc procs[] = {
46 	{ "parent",	PROC_PARENT,	priv_dispatch_parent }
47 };
48 
49 void
50 priv(struct privsep *ps, struct privsep_proc *p)
51 {
52 	proc_run(ps, p, procs, nitems(procs), priv_run, NULL);
53 }
54 
55 void
56 priv_run(struct privsep *ps, struct privsep_proc *p, void *arg)
57 {
58 	struct vmd		*env = ps->ps_env;
59 
60 	/*
61 	 * no pledge(2) in the "priv" process:
62 	 * write ioctls are not permitted by pledge.
63 	 */
64 
65 	/* Open our own socket for generic interface ioctls */
66 	if ((env->vmd_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
67 		fatal("socket");
68 
69 	/* But we need a different fd for IPv6 */
70 	if ((env->vmd_fd6 = socket(AF_INET6, SOCK_DGRAM, 0)) == -1)
71 		fatal("socket6");
72 }
73 
74 int
75 priv_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
76 {
77 	const char		*desct[] = { "tap", "bridge", "veb", NULL };
78 	struct privsep		*ps = p->p_ps;
79 	struct vmop_ifreq	 vfr;
80 	struct vmd		*env = ps->ps_env;
81 	struct ifreq		 ifr;
82 	struct ifbreq		 ifbr;
83 	struct ifgroupreq	 ifgr;
84 	struct ifaliasreq	 ifra;
85 	struct in6_aliasreq	 in6_ifra;
86 	struct vmop_addr_req	 vareq;
87 	struct vmop_addr_result	 varesult;
88 	char			 type[IF_NAMESIZE];
89 	int			 ifd;
90 
91 	switch (imsg->hdr.type) {
92 	case IMSG_VMDOP_PRIV_IFDESCR:
93 	case IMSG_VMDOP_PRIV_IFRDOMAIN:
94 	case IMSG_VMDOP_PRIV_IFEXISTS:
95 	case IMSG_VMDOP_PRIV_IFADD:
96 	case IMSG_VMDOP_PRIV_IFUP:
97 	case IMSG_VMDOP_PRIV_IFDOWN:
98 	case IMSG_VMDOP_PRIV_IFGROUP:
99 	case IMSG_VMDOP_PRIV_IFADDR:
100 	case IMSG_VMDOP_PRIV_IFADDR6:
101 		IMSG_SIZE_CHECK(imsg, &vfr);
102 		memcpy(&vfr, imsg->data, sizeof(vfr));
103 
104 		/* We should not get malicious requests from the parent */
105 		if (priv_getiftype(vfr.vfr_name, type, NULL) == -1 ||
106 		    priv_findname(type, desct) == -1)
107 			fatalx("%s: rejected priv operation on interface: %s",
108 			    __func__, vfr.vfr_name);
109 		break;
110 	case IMSG_VMDOP_CONFIG:
111 	case IMSG_CTL_RESET:
112 	case IMSG_VMDOP_PRIV_GET_ADDR:
113 		break;
114 	default:
115 		return (-1);
116 	}
117 
118 	switch (imsg->hdr.type) {
119 	case IMSG_VMDOP_PRIV_IFDESCR:
120 		/* Set the interface description */
121 		strlcpy(ifr.ifr_name, vfr.vfr_name, sizeof(ifr.ifr_name));
122 		ifr.ifr_data = (caddr_t)vfr.vfr_value;
123 		if (ioctl(env->vmd_fd, SIOCSIFDESCR, &ifr) == -1)
124 			log_warn("SIOCSIFDESCR");
125 		break;
126 	case IMSG_VMDOP_PRIV_IFRDOMAIN:
127 		strlcpy(ifr.ifr_name, vfr.vfr_name, sizeof(ifr.ifr_name));
128 		ifr.ifr_rdomainid = vfr.vfr_id;
129 		if (ioctl(env->vmd_fd, SIOCSIFRDOMAIN, &ifr) == -1)
130 			log_warn("SIOCSIFRDOMAIN");
131 		break;
132 	case IMSG_VMDOP_PRIV_IFADD:
133 		if (priv_getiftype(vfr.vfr_value, type, NULL) == -1)
134 			fatalx("%s: rejected to add interface: %s",
135 			    __func__, vfr.vfr_value);
136 
137 		/* Attach the device to the bridge */
138 		strlcpy(ifbr.ifbr_name, vfr.vfr_name,
139 		    sizeof(ifbr.ifbr_name));
140 		strlcpy(ifbr.ifbr_ifsname, vfr.vfr_value,
141 		    sizeof(ifbr.ifbr_ifsname));
142 		if (ioctl(env->vmd_fd, SIOCBRDGADD, &ifbr) == -1 &&
143 		    errno != EEXIST)
144 			log_warn("SIOCBRDGADD");
145 		break;
146 	case IMSG_VMDOP_PRIV_IFEXISTS:
147 		/* Determine if bridge exists */
148 		strlcpy(ifr.ifr_name, vfr.vfr_name, sizeof(ifr.ifr_name));
149 		if (ioctl(env->vmd_fd, SIOCGIFFLAGS, &ifr) == -1)
150 			fatalx("%s: bridge \"%s\" does not exist",
151 			    __func__, vfr.vfr_name);
152 		break;
153 	case IMSG_VMDOP_PRIV_IFUP:
154 	case IMSG_VMDOP_PRIV_IFDOWN:
155 		/* Set the interface status */
156 		strlcpy(ifr.ifr_name, vfr.vfr_name, sizeof(ifr.ifr_name));
157 		if (ioctl(env->vmd_fd, SIOCGIFFLAGS, &ifr) == -1) {
158 			log_warn("SIOCGIFFLAGS");
159 			break;
160 		}
161 		if (imsg->hdr.type == IMSG_VMDOP_PRIV_IFUP)
162 			ifr.ifr_flags |= IFF_UP;
163 		else
164 			ifr.ifr_flags &= ~IFF_UP;
165 		if (ioctl(env->vmd_fd, SIOCSIFFLAGS, &ifr) == -1)
166 			log_warn("SIOCSIFFLAGS");
167 		break;
168 	case IMSG_VMDOP_PRIV_IFGROUP:
169 		if (priv_validgroup(vfr.vfr_value) == -1)
170 			fatalx("%s: invalid group name", __func__);
171 
172 		if (strlcpy(ifgr.ifgr_name, vfr.vfr_name,
173 		    sizeof(ifgr.ifgr_name)) >= sizeof(ifgr.ifgr_name) ||
174 		    strlcpy(ifgr.ifgr_group, vfr.vfr_value,
175 		    sizeof(ifgr.ifgr_group)) >= sizeof(ifgr.ifgr_group))
176 			fatalx("%s: group name too long", __func__);
177 
178 		if (ioctl(env->vmd_fd, SIOCAIFGROUP, &ifgr) == -1 &&
179 		    errno != EEXIST)
180 			log_warn("SIOCAIFGROUP");
181 		break;
182 	case IMSG_VMDOP_PRIV_IFADDR:
183 		memset(&ifra, 0, sizeof(ifra));
184 
185 		if (vfr.vfr_addr.ss_family != AF_INET ||
186 		    vfr.vfr_addr.ss_family != vfr.vfr_mask.ss_family)
187 			fatalx("%s: invalid address family", __func__);
188 
189 		/* Set the interface address */
190 		strlcpy(ifra.ifra_name, vfr.vfr_name, sizeof(ifra.ifra_name));
191 
192 		memcpy(&ifra.ifra_addr, &vfr.vfr_addr,
193 		    sizeof(ifra.ifra_addr));
194 		memcpy(&ifra.ifra_mask, &vfr.vfr_mask,
195 		    sizeof(ifra.ifra_mask));
196 
197 		if (ioctl(env->vmd_fd, SIOCAIFADDR, &ifra) == -1)
198 			log_warn("SIOCAIFADDR");
199 		break;
200 	case IMSG_VMDOP_PRIV_IFADDR6:
201 		memset(&in6_ifra, 0, sizeof(in6_ifra));
202 
203 		if (vfr.vfr_addr.ss_family != AF_INET6 ||
204 		    vfr.vfr_addr.ss_family != vfr.vfr_mask.ss_family)
205 			fatalx("%s: invalid address family", __func__);
206 
207 		/* Set the interface address */
208 		strlcpy(in6_ifra.ifra_name, vfr.vfr_name,
209 		    sizeof(in6_ifra.ifra_name));
210 
211 		memcpy(&in6_ifra.ifra_addr, &vfr.vfr_addr,
212 		    sizeof(in6_ifra.ifra_addr));
213 		memcpy(&in6_ifra.ifra_prefixmask, &vfr.vfr_mask,
214 		    sizeof(in6_ifra.ifra_prefixmask));
215 		in6_ifra.ifra_prefixmask.sin6_scope_id = 0;
216 
217 		in6_ifra.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
218 		in6_ifra.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
219 
220 		if (ioctl(env->vmd_fd6, SIOCDIFADDR_IN6, &in6_ifra) == -1 &&
221 		    errno != EADDRNOTAVAIL)
222 			log_warn("SIOCDIFADDR_IN6");
223 
224 		if (ioctl(env->vmd_fd6, SIOCAIFADDR_IN6, &in6_ifra) == -1)
225 			log_warn("SIOCAIFADDR_IN6");
226 		break;
227 	case IMSG_VMDOP_PRIV_GET_ADDR:
228 		IMSG_SIZE_CHECK(imsg, &vareq);
229 		memcpy(&vareq, imsg->data, sizeof(vareq));
230 
231 		varesult.var_vmid = vareq.var_vmid;
232 		varesult.var_nic_idx = vareq.var_nic_idx;
233 
234 		ifd = imsg_get_fd(imsg);
235 		/* resolve lladdr for the tap(4) and send back to parent */
236 		if (ioctl(ifd, SIOCGIFADDR, &varesult.var_addr) != 0)
237 			log_warn("SIOCGIFADDR");
238 		else
239 			proc_compose_imsg(ps, PROC_PARENT, -1,
240 			    IMSG_VMDOP_PRIV_GET_ADDR_RESPONSE, imsg->hdr.peerid,
241 			    -1, &varesult, sizeof(varesult));
242 		close(ifd);
243 		break;
244 	case IMSG_VMDOP_CONFIG:
245 		config_getconfig(env, imsg);
246 		break;
247 	case IMSG_CTL_RESET:
248 		config_getreset(env, imsg);
249 		break;
250 	default:
251 		return (-1);
252 	}
253 
254 	return (0);
255 }
256 
257 int
258 priv_getiftype(char *ifname, char *type, unsigned int *unitptr)
259 {
260 	const char	*errstr;
261 	size_t		 span;
262 	unsigned int	 unit;
263 
264 	/* Extract the name part */
265 	span = strcspn(ifname, "0123456789");
266 	if (span == 0 || span >= strlen(ifname) || span >= (IF_NAMESIZE - 1))
267 		return (-1);
268 	memcpy(type, ifname, span);
269 	type[span] = 0;
270 
271 	/* Now parse the unit (we don't strictly validate the format here) */
272 	unit = strtonum(ifname + span, 0, UINT_MAX, &errstr);
273 	if (errstr != NULL)
274 		return (-1);
275 	if (unitptr != NULL)
276 		*unitptr = unit;
277 
278 	return (0);
279 }
280 
281 int
282 priv_findname(const char *name, const char **names)
283 {
284 	unsigned int	 i;
285 
286 	for (i = 0; names[i] != NULL; i++) {
287 		if (strcmp(name, names[i]) == 0)
288 			return (0);
289 	}
290 
291 	return (-1);
292 }
293 
294 int
295 priv_validgroup(const char *name)
296 {
297 	const size_t len = strnlen(name, IF_NAMESIZE);
298 
299 	if (len == IF_NAMESIZE)
300 		return (-1);
301 	/* Group can not end with a digit */
302 	if (len > 0 && isdigit((unsigned char)name[len - 1]))
303 		return (-1);
304 	return (0);
305 }
306 
307 /*
308  * Called from the Parent process to setup vm interface(s)
309  * - ensure the interface has the description set (tracking purposes)
310  * - if interface is to be attached to a switch, attach it
311  * - check if rdomain is set on interface and switch
312  *   - if interface only or both, use interface rdomain
313  *   - if switch only, use switch rdomain
314  * - check if group is set on interface and switch
315  *   - if interface, add it
316  *   - if switch, add it
317  * - ensure the interface is up/down
318  * - if local interface, set address
319  */
320 int
321 vm_priv_ifconfig(struct privsep *ps, struct vmd_vm *vm)
322 {
323 	char			 name[64];
324 	struct vmd		*env = ps->ps_env;
325 	struct vm_create_params	*vcp = &vm->vm_params.vmc_params;
326 	struct vmd_if		*vif;
327 	struct vmd_switch	*vsw;
328 	unsigned int		 i;
329 	struct vmop_ifreq	 vfr, vfbr;
330 	struct sockaddr_in	*sin4;
331 	struct sockaddr_in6	*sin6;
332 
333 	for (i = 0; i < VM_MAX_NICS_PER_VM; i++) {
334 		vif = &vm->vm_ifs[i];
335 
336 		if (vif->vif_name == NULL)
337 			break;
338 
339 		memset(&vfr, 0, sizeof(vfr));
340 		if (strlcpy(vfr.vfr_name, vif->vif_name,
341 		    sizeof(vfr.vfr_name)) >= sizeof(vfr.vfr_name))
342 			return (-1);
343 
344 		/* Description can be truncated */
345 		(void)snprintf(vfr.vfr_value, sizeof(vfr.vfr_value),
346 		    "vm%u-if%u-%s", vm->vm_vmid, i, vcp->vcp_name);
347 
348 		log_debug("%s: interface %s description %s", __func__,
349 		    vfr.vfr_name, vfr.vfr_value);
350 
351 		proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFDESCR,
352 		    &vfr, sizeof(vfr));
353 
354 		/* set default rdomain */
355 		vfr.vfr_id = getrtable();
356 
357 		vsw = switch_getbyname(vif->vif_switch);
358 
359 		/* Check if switch should exist */
360 		if (vsw == NULL && vif->vif_switch != NULL)
361 			log_warnx("switch \"%s\" not found", vif->vif_switch);
362 
363 		/* Add interface to switch and set proper rdomain */
364 		if (vsw != NULL) {
365 			memset(&vfbr, 0, sizeof(vfbr));
366 
367 			if (strlcpy(vfbr.vfr_name, vsw->sw_ifname,
368 			    sizeof(vfbr.vfr_name)) >= sizeof(vfbr.vfr_name))
369 				return (-1);
370 			if (strlcpy(vfbr.vfr_value, vif->vif_name,
371 			    sizeof(vfbr.vfr_value)) >= sizeof(vfbr.vfr_value))
372 				return (-1);
373 
374 			log_debug("%s: switch \"%s\" interface %s add %s",
375 			    __func__, vsw->sw_name, vfbr.vfr_name,
376 			    vfbr.vfr_value);
377 
378 			proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFADD,
379 			    &vfbr, sizeof(vfbr));
380 
381 			/* Check rdomain properties */
382 			if (vif->vif_flags & VMIFF_RDOMAIN)
383 				vfr.vfr_id = vif->vif_rdomain;
384 			else if (vsw->sw_flags & VMIFF_RDOMAIN)
385 				vfr.vfr_id = vsw->sw_rdomain;
386 		} else {
387 			/* No switch to attach case */
388 			if (vif->vif_flags & VMIFF_RDOMAIN)
389 				vfr.vfr_id = vif->vif_rdomain;
390 		}
391 
392 		/* Set rdomain on interface */
393 		if (vfr.vfr_id != 0)
394 			log_debug("%s: interface %s rdomain %u", __func__,
395 			    vfr.vfr_name, vfr.vfr_id);
396 
397 		proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFRDOMAIN,
398 		    &vfr, sizeof(vfr));
399 
400 		/* First group is defined per-interface */
401 		if (vif->vif_group) {
402 			if (strlcpy(vfr.vfr_value, vif->vif_group,
403 			    sizeof(vfr.vfr_value)) >= sizeof(vfr.vfr_value))
404 				return (-1);
405 
406 			log_debug("%s: interface %s group %s", __func__,
407 			    vfr.vfr_name, vfr.vfr_value);
408 
409 			proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFGROUP,
410 			    &vfr, sizeof(vfr));
411 		}
412 
413 		/* The second group is defined per-switch */
414 		if (vsw != NULL && vsw->sw_group != NULL) {
415 			if (strlcpy(vfr.vfr_value, vsw->sw_group,
416 			    sizeof(vfr.vfr_value)) >= sizeof(vfr.vfr_value))
417 				return (-1);
418 
419 			log_debug("%s: interface %s group %s switch \"%s\"",
420 			    __func__, vfr.vfr_name, vfr.vfr_value,
421 			    vsw->sw_name);
422 
423 			proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFGROUP,
424 			    &vfr, sizeof(vfr));
425 		}
426 
427 		/* Set the new interface status to up or down */
428 		proc_compose(ps, PROC_PRIV, (vif->vif_flags & VMIFF_UP) ?
429 		    IMSG_VMDOP_PRIV_IFUP : IMSG_VMDOP_PRIV_IFDOWN,
430 		    &vfr, sizeof(vfr));
431 
432 		/* Set interface address if it is a local interface */
433 		if (vm->vm_params.vmc_ifflags[i] & VMIFF_LOCAL) {
434 			memset(&vfr.vfr_mask, 0, sizeof(vfr.vfr_mask));
435 			memset(&vfr.vfr_addr, 0, sizeof(vfr.vfr_addr));
436 
437 			/* local IPv4 address with a /31 mask */
438 			sin4 = (struct sockaddr_in *)&vfr.vfr_mask;
439 			sin4->sin_family = AF_INET;
440 			sin4->sin_len = sizeof(*sin4);
441 			sin4->sin_addr.s_addr = htonl(0xfffffffe);
442 
443 			sin4 = (struct sockaddr_in *)&vfr.vfr_addr;
444 			sin4->sin_family = AF_INET;
445 			sin4->sin_len = sizeof(*sin4);
446 			if ((sin4->sin_addr.s_addr =
447 			    vm_priv_addr(&env->vmd_cfg.cfg_localprefix,
448 			    vm->vm_vmid, i, 0)) == 0)
449 				return (-1);
450 
451 			inet_ntop(AF_INET, &sin4->sin_addr,
452 			    name, sizeof(name));
453 			log_debug("%s: interface %s address %s/31",
454 			    __func__, vfr.vfr_name, name);
455 
456 			proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFADDR,
457 			    &vfr, sizeof(vfr));
458 		}
459 		if ((vm->vm_params.vmc_ifflags[i] & VMIFF_LOCAL) &&
460 		    (env->vmd_cfg.cfg_flags & VMD_CFG_INET6)) {
461 			memset(&vfr.vfr_mask, 0, sizeof(vfr.vfr_mask));
462 			memset(&vfr.vfr_addr, 0, sizeof(vfr.vfr_addr));
463 
464 			/* local IPv6 address with a /96 mask */
465 			sin6 = ss2sin6(&vfr.vfr_mask);
466 			sin6->sin6_family = AF_INET6;
467 			sin6->sin6_len = sizeof(*sin6);
468 			memset(&sin6->sin6_addr.s6_addr[0], 0xff, 12);
469 			memset(&sin6->sin6_addr.s6_addr[12], 0, 4);
470 
471 			sin6 = ss2sin6(&vfr.vfr_addr);
472 			sin6->sin6_family = AF_INET6;
473 			sin6->sin6_len = sizeof(*sin6);
474 			if (vm_priv_addr6(&env->vmd_cfg.cfg_localprefix,
475 			    vm->vm_vmid, i, 0, &sin6->sin6_addr) == -1)
476 				return (-1);
477 
478 			inet_ntop(AF_INET6, &sin6->sin6_addr,
479 			    name, sizeof(name));
480 			log_debug("%s: interface %s address %s/96",
481 			    __func__, vfr.vfr_name, name);
482 
483 			proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFADDR6,
484 			    &vfr, sizeof(vfr));
485 		}
486 	}
487 
488 	return (0);
489 }
490 
491 /*
492  * Called from the Parent process to setup underlying switch interface
493  * - ensure the interface exists
494  * - ensure the interface has the correct rdomain set
495  * - ensure the interface has the description set (tracking purposes)
496  * - ensure the interface is up/down
497  */
498 int
499 vm_priv_brconfig(struct privsep *ps, struct vmd_switch *vsw)
500 {
501 	struct vmop_ifreq	 vfr;
502 
503 	memset(&vfr, 0, sizeof(vfr));
504 
505 	if (strlcpy(vfr.vfr_name, vsw->sw_ifname,
506 	    sizeof(vfr.vfr_name)) >= sizeof(vfr.vfr_name))
507 		return (-1);
508 
509 	/* ensure bridge exists */
510 	proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFEXISTS,
511 	    &vfr, sizeof(vfr));
512 
513 	/* Use the configured rdomain or get it from the process */
514 	if (vsw->sw_flags & VMIFF_RDOMAIN)
515 		vfr.vfr_id = vsw->sw_rdomain;
516 	else
517 		vfr.vfr_id = getrtable();
518 	if (vfr.vfr_id != 0)
519 		log_debug("%s: interface %s rdomain %u", __func__,
520 		    vfr.vfr_name, vfr.vfr_id);
521 
522 	/* ensure bridge has the correct rdomain */
523 	proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFRDOMAIN,
524 	    &vfr, sizeof(vfr));
525 
526 	/* Description can be truncated */
527 	(void)snprintf(vfr.vfr_value, sizeof(vfr.vfr_value),
528 	    "switch%u-%s", vsw->sw_id, vsw->sw_name);
529 
530 	log_debug("%s: interface %s description %s", __func__,
531 	    vfr.vfr_name, vfr.vfr_value);
532 
533 	proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFDESCR,
534 	    &vfr, sizeof(vfr));
535 
536 	/* Set the new interface status to up or down */
537 	proc_compose(ps, PROC_PRIV, (vsw->sw_flags & VMIFF_UP) ?
538 	    IMSG_VMDOP_PRIV_IFUP : IMSG_VMDOP_PRIV_IFDOWN,
539 	    &vfr, sizeof(vfr));
540 
541 	vsw->sw_running = 1;
542 	return (0);
543 }
544 
545 uint32_t
546 vm_priv_addr(struct local_prefix *p, uint32_t vmid, int idx, int isvm)
547 {
548 	in_addr_t		 addr;
549 
550 	/* Encode the VM ID as a per-VM subnet range N, 100.64.N.0/24. */
551 	addr = vmid << 8;
552 
553 	/*
554 	 * Assign a /31 subnet M per VM interface, 100.64.N.M/31.
555 	 * Each subnet contains exactly two IP addresses; skip the
556 	 * first subnet to avoid a gateway address ending with .0.
557 	 */
558 	addr |= (idx + 1) * 2;
559 
560 	/* Use the first address for the gateway, the second for the VM. */
561 	if (isvm)
562 		addr++;
563 
564 	/* Convert to network byte order and add the prefix. */
565 	addr = htonl(addr) | p->lp_in.s_addr;
566 
567 	/*
568 	 * Validate the results:
569 	 * - the address should not exceed the prefix (eg. VM ID to high).
570 	 * - up to 126 interfaces can be encoded per VM.
571 	 */
572 	if (p->lp_in.s_addr != (addr & p->lp_mask.s_addr) || idx >= 0x7f) {
573 		log_warnx("%s: dhcp address range exceeded,"
574 		    " vm id %u interface %d", __func__, vmid, idx);
575 		return (0);
576 	}
577 
578 	return (addr);
579 }
580 
581 int
582 vm_priv_addr6(struct local_prefix *p, uint32_t vmid, int idx, int isvm,
583     struct in6_addr *out)
584 {
585 	struct in6_addr		 addr;
586 	in_addr_t		 addr4;
587 
588 	/* Start with the IPv6 prefix. */
589 	memcpy(&addr, &p->lp_in6, sizeof(addr));
590 
591 	/* Encode the VM IPv4 address as subnet, fd00::NN:NN:0:0/96. */
592 	if ((addr4 = vm_priv_addr(p, vmid, idx, 1)) == 0)
593 		return (0);
594 	memcpy(&addr.s6_addr[8], &addr4, sizeof(addr4));
595 
596 	/*
597 	 * Set the last octet to 1 (host) or 2 (VM).
598 	 * The latter is currently not used inside vmd as we don't
599 	 * answer rtsol requests ourselves.
600 	 */
601 	if (!isvm)
602 		addr.s6_addr[15] = 1;
603 	else
604 		addr.s6_addr[15] = 2;
605 
606 	memcpy(out, &addr, sizeof(*out));
607 
608 	return (0);
609 }
610