xref: /openbsd-src/usr.sbin/vmd/priv.c (revision 4e55220c92ad2b00df4c44125391f54be0e401da)
1*4e55220cSkn /*	$OpenBSD: priv.c,v 1.27 2024/11/24 10:44:59 kn Exp $	*/
25921535cSreyk 
35921535cSreyk /*
45921535cSreyk  * Copyright (c) 2016 Reyk Floeter <reyk@openbsd.org>
55921535cSreyk  *
65921535cSreyk  * Permission to use, copy, modify, and distribute this software for any
75921535cSreyk  * purpose with or without fee is hereby granted, provided that the above
85921535cSreyk  * copyright notice and this permission notice appear in all copies.
95921535cSreyk  *
105921535cSreyk  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
115921535cSreyk  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
125921535cSreyk  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
135921535cSreyk  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
145921535cSreyk  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
155921535cSreyk  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
165921535cSreyk  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
175921535cSreyk  */
185921535cSreyk 
1924fb43d0Sderaadt #include <sys/types.h>
205921535cSreyk #include <sys/socket.h>
215921535cSreyk #include <sys/ioctl.h>
225921535cSreyk 
235921535cSreyk #include <net/if.h>
24789e0822Sreyk #include <netinet/in.h>
25789e0822Sreyk #include <netinet/if_ether.h>
26723f86d2Sreyk #include <netinet6/in6_var.h>
27723f86d2Sreyk #include <netinet6/nd6.h>
28789e0822Sreyk #include <net/if_bridge.h>
295921535cSreyk 
30470adcf5Sreyk #include <arpa/inet.h>
31470adcf5Sreyk 
325921535cSreyk #include <errno.h>
335921535cSreyk #include <stdlib.h>
345921535cSreyk #include <stdio.h>
355921535cSreyk #include <string.h>
365921535cSreyk #include <unistd.h>
372b519c1fSreyk #include <ctype.h>
385921535cSreyk 
395921535cSreyk #include "proc.h"
405921535cSreyk #include "vmd.h"
415921535cSreyk 
425921535cSreyk int	 priv_dispatch_parent(int, struct privsep_proc *, struct imsg *);
435921535cSreyk void	 priv_run(struct privsep *, struct privsep_proc *, void *);
445921535cSreyk 
455921535cSreyk static struct privsep_proc procs[] = {
465921535cSreyk 	{ "parent",	PROC_PARENT,	priv_dispatch_parent }
475921535cSreyk };
485921535cSreyk 
495921535cSreyk void
505921535cSreyk priv(struct privsep *ps, struct privsep_proc *p)
515921535cSreyk {
525921535cSreyk 	proc_run(ps, p, procs, nitems(procs), priv_run, NULL);
535921535cSreyk }
545921535cSreyk 
555921535cSreyk void
565921535cSreyk priv_run(struct privsep *ps, struct privsep_proc *p, void *arg)
575921535cSreyk {
585921535cSreyk 	struct vmd		*env = ps->ps_env;
595921535cSreyk 
605921535cSreyk 	/*
615921535cSreyk 	 * no pledge(2) in the "priv" process:
625921535cSreyk 	 * write ioctls are not permitted by pledge.
635921535cSreyk 	 */
645921535cSreyk 
655921535cSreyk 	/* Open our own socket for generic interface ioctls */
665921535cSreyk 	if ((env->vmd_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
675921535cSreyk 		fatal("socket");
68723f86d2Sreyk 
69723f86d2Sreyk 	/* But we need a different fd for IPv6 */
70723f86d2Sreyk 	if ((env->vmd_fd6 = socket(AF_INET6, SOCK_DGRAM, 0)) == -1)
71723f86d2Sreyk 		fatal("socket6");
725921535cSreyk }
735921535cSreyk 
745921535cSreyk int
755921535cSreyk priv_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
765921535cSreyk {
7740d1acf5Ssthen 	const char		*desct[] = { "tap", "bridge", "veb", NULL };
785921535cSreyk 	struct privsep		*ps = p->p_ps;
795921535cSreyk 	struct vmop_ifreq	 vfr;
805921535cSreyk 	struct vmd		*env = ps->ps_env;
815921535cSreyk 	struct ifreq		 ifr;
82789e0822Sreyk 	struct ifbreq		 ifbr;
832b519c1fSreyk 	struct ifgroupreq	 ifgr;
84470adcf5Sreyk 	struct ifaliasreq	 ifra;
85723f86d2Sreyk 	struct in6_aliasreq	 in6_ifra;
8697f33f1dSdv 	struct vmop_addr_req	 vareq;
8797f33f1dSdv 	struct vmop_addr_result	 varesult;
885921535cSreyk 	char			 type[IF_NAMESIZE];
8953027660Sclaudio 	int			 ifd;
905921535cSreyk 
915921535cSreyk 	switch (imsg->hdr.type) {
925921535cSreyk 	case IMSG_VMDOP_PRIV_IFDESCR:
93f418e70cSreyk 	case IMSG_VMDOP_PRIV_IFRDOMAIN:
94a123de80Smlarkin 	case IMSG_VMDOP_PRIV_IFEXISTS:
95789e0822Sreyk 	case IMSG_VMDOP_PRIV_IFADD:
96789e0822Sreyk 	case IMSG_VMDOP_PRIV_IFUP:
97789e0822Sreyk 	case IMSG_VMDOP_PRIV_IFDOWN:
982b519c1fSreyk 	case IMSG_VMDOP_PRIV_IFGROUP:
99470adcf5Sreyk 	case IMSG_VMDOP_PRIV_IFADDR:
100723f86d2Sreyk 	case IMSG_VMDOP_PRIV_IFADDR6:
1015921535cSreyk 		IMSG_SIZE_CHECK(imsg, &vfr);
1025921535cSreyk 		memcpy(&vfr, imsg->data, sizeof(vfr));
1035921535cSreyk 
1045921535cSreyk 		/* We should not get malicious requests from the parent */
105789e0822Sreyk 		if (priv_getiftype(vfr.vfr_name, type, NULL) == -1 ||
1065921535cSreyk 		    priv_findname(type, desct) == -1)
1075921535cSreyk 			fatalx("%s: rejected priv operation on interface: %s",
1085921535cSreyk 			    __func__, vfr.vfr_name);
109789e0822Sreyk 		break;
110c48cfcf4Sreyk 	case IMSG_VMDOP_CONFIG:
111c48cfcf4Sreyk 	case IMSG_CTL_RESET:
11297f33f1dSdv 	case IMSG_VMDOP_PRIV_GET_ADDR:
113c48cfcf4Sreyk 		break;
114789e0822Sreyk 	default:
115789e0822Sreyk 		return (-1);
116789e0822Sreyk 	}
1175921535cSreyk 
118789e0822Sreyk 	switch (imsg->hdr.type) {
119789e0822Sreyk 	case IMSG_VMDOP_PRIV_IFDESCR:
120789e0822Sreyk 		/* Set the interface description */
1215921535cSreyk 		strlcpy(ifr.ifr_name, vfr.vfr_name, sizeof(ifr.ifr_name));
1225921535cSreyk 		ifr.ifr_data = (caddr_t)vfr.vfr_value;
123df69c215Sderaadt 		if (ioctl(env->vmd_fd, SIOCSIFDESCR, &ifr) == -1)
1245921535cSreyk 			log_warn("SIOCSIFDESCR");
1255921535cSreyk 		break;
126f418e70cSreyk 	case IMSG_VMDOP_PRIV_IFRDOMAIN:
127f418e70cSreyk 		strlcpy(ifr.ifr_name, vfr.vfr_name, sizeof(ifr.ifr_name));
128f418e70cSreyk 		ifr.ifr_rdomainid = vfr.vfr_id;
129df69c215Sderaadt 		if (ioctl(env->vmd_fd, SIOCSIFRDOMAIN, &ifr) == -1)
130f418e70cSreyk 			log_warn("SIOCSIFRDOMAIN");
131f418e70cSreyk 		break;
132789e0822Sreyk 	case IMSG_VMDOP_PRIV_IFADD:
133789e0822Sreyk 		if (priv_getiftype(vfr.vfr_value, type, NULL) == -1)
134789e0822Sreyk 			fatalx("%s: rejected to add interface: %s",
135789e0822Sreyk 			    __func__, vfr.vfr_value);
136789e0822Sreyk 
137789e0822Sreyk 		/* Attach the device to the bridge */
138789e0822Sreyk 		strlcpy(ifbr.ifbr_name, vfr.vfr_name,
139789e0822Sreyk 		    sizeof(ifbr.ifbr_name));
140789e0822Sreyk 		strlcpy(ifbr.ifbr_ifsname, vfr.vfr_value,
141789e0822Sreyk 		    sizeof(ifbr.ifbr_ifsname));
142df69c215Sderaadt 		if (ioctl(env->vmd_fd, SIOCBRDGADD, &ifbr) == -1 &&
143789e0822Sreyk 		    errno != EEXIST)
144789e0822Sreyk 			log_warn("SIOCBRDGADD");
145789e0822Sreyk 		break;
146a123de80Smlarkin 	case IMSG_VMDOP_PRIV_IFEXISTS:
14740d1acf5Ssthen 		/* Determine if bridge exists */
148a123de80Smlarkin 		strlcpy(ifr.ifr_name, vfr.vfr_name, sizeof(ifr.ifr_name));
149df69c215Sderaadt 		if (ioctl(env->vmd_fd, SIOCGIFFLAGS, &ifr) == -1)
150a123de80Smlarkin 			fatalx("%s: bridge \"%s\" does not exist",
151a123de80Smlarkin 			    __func__, vfr.vfr_name);
152a123de80Smlarkin 		break;
153789e0822Sreyk 	case IMSG_VMDOP_PRIV_IFUP:
154789e0822Sreyk 	case IMSG_VMDOP_PRIV_IFDOWN:
155789e0822Sreyk 		/* Set the interface status */
156789e0822Sreyk 		strlcpy(ifr.ifr_name, vfr.vfr_name, sizeof(ifr.ifr_name));
157df69c215Sderaadt 		if (ioctl(env->vmd_fd, SIOCGIFFLAGS, &ifr) == -1) {
158789e0822Sreyk 			log_warn("SIOCGIFFLAGS");
159789e0822Sreyk 			break;
160789e0822Sreyk 		}
161789e0822Sreyk 		if (imsg->hdr.type == IMSG_VMDOP_PRIV_IFUP)
162789e0822Sreyk 			ifr.ifr_flags |= IFF_UP;
163789e0822Sreyk 		else
164789e0822Sreyk 			ifr.ifr_flags &= ~IFF_UP;
165df69c215Sderaadt 		if (ioctl(env->vmd_fd, SIOCSIFFLAGS, &ifr) == -1)
166789e0822Sreyk 			log_warn("SIOCSIFFLAGS");
167789e0822Sreyk 		break;
1682b519c1fSreyk 	case IMSG_VMDOP_PRIV_IFGROUP:
1692b519c1fSreyk 		if (priv_validgroup(vfr.vfr_value) == -1)
1702b519c1fSreyk 			fatalx("%s: invalid group name", __func__);
1712b519c1fSreyk 
1722b519c1fSreyk 		if (strlcpy(ifgr.ifgr_name, vfr.vfr_name,
1732b519c1fSreyk 		    sizeof(ifgr.ifgr_name)) >= sizeof(ifgr.ifgr_name) ||
1742b519c1fSreyk 		    strlcpy(ifgr.ifgr_group, vfr.vfr_value,
1752b519c1fSreyk 		    sizeof(ifgr.ifgr_group)) >= sizeof(ifgr.ifgr_group))
1762b519c1fSreyk 			fatalx("%s: group name too long", __func__);
1772b519c1fSreyk 
178df69c215Sderaadt 		if (ioctl(env->vmd_fd, SIOCAIFGROUP, &ifgr) == -1 &&
1792b519c1fSreyk 		    errno != EEXIST)
1802b519c1fSreyk 			log_warn("SIOCAIFGROUP");
1812b519c1fSreyk 		break;
182470adcf5Sreyk 	case IMSG_VMDOP_PRIV_IFADDR:
183470adcf5Sreyk 		memset(&ifra, 0, sizeof(ifra));
184470adcf5Sreyk 
185723f86d2Sreyk 		if (vfr.vfr_addr.ss_family != AF_INET ||
186723f86d2Sreyk 		    vfr.vfr_addr.ss_family != vfr.vfr_mask.ss_family)
187723f86d2Sreyk 			fatalx("%s: invalid address family", __func__);
188723f86d2Sreyk 
189470adcf5Sreyk 		/* Set the interface address */
190470adcf5Sreyk 		strlcpy(ifra.ifra_name, vfr.vfr_name, sizeof(ifra.ifra_name));
191470adcf5Sreyk 
192723f86d2Sreyk 		memcpy(&ifra.ifra_addr, &vfr.vfr_addr,
193*4e55220cSkn 		    sizeof(ifra.ifra_addr));
194723f86d2Sreyk 		memcpy(&ifra.ifra_mask, &vfr.vfr_mask,
195*4e55220cSkn 		    sizeof(ifra.ifra_mask));
196470adcf5Sreyk 
197df69c215Sderaadt 		if (ioctl(env->vmd_fd, SIOCAIFADDR, &ifra) == -1)
198470adcf5Sreyk 			log_warn("SIOCAIFADDR");
199470adcf5Sreyk 		break;
200723f86d2Sreyk 	case IMSG_VMDOP_PRIV_IFADDR6:
201723f86d2Sreyk 		memset(&in6_ifra, 0, sizeof(in6_ifra));
202723f86d2Sreyk 
203723f86d2Sreyk 		if (vfr.vfr_addr.ss_family != AF_INET6 ||
204723f86d2Sreyk 		    vfr.vfr_addr.ss_family != vfr.vfr_mask.ss_family)
205723f86d2Sreyk 			fatalx("%s: invalid address family", __func__);
206723f86d2Sreyk 
207723f86d2Sreyk 		/* Set the interface address */
208723f86d2Sreyk 		strlcpy(in6_ifra.ifra_name, vfr.vfr_name,
209723f86d2Sreyk 		    sizeof(in6_ifra.ifra_name));
210723f86d2Sreyk 
211723f86d2Sreyk 		memcpy(&in6_ifra.ifra_addr, &vfr.vfr_addr,
212*4e55220cSkn 		    sizeof(in6_ifra.ifra_addr));
213723f86d2Sreyk 		memcpy(&in6_ifra.ifra_prefixmask, &vfr.vfr_mask,
214*4e55220cSkn 		    sizeof(in6_ifra.ifra_prefixmask));
215723f86d2Sreyk 		in6_ifra.ifra_prefixmask.sin6_scope_id = 0;
216723f86d2Sreyk 
217723f86d2Sreyk 		in6_ifra.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
218723f86d2Sreyk 		in6_ifra.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
219723f86d2Sreyk 
220df69c215Sderaadt 		if (ioctl(env->vmd_fd6, SIOCDIFADDR_IN6, &in6_ifra) == -1 &&
221723f86d2Sreyk 		    errno != EADDRNOTAVAIL)
222723f86d2Sreyk 			log_warn("SIOCDIFADDR_IN6");
223723f86d2Sreyk 
224df69c215Sderaadt 		if (ioctl(env->vmd_fd6, SIOCAIFADDR_IN6, &in6_ifra) == -1)
225723f86d2Sreyk 			log_warn("SIOCAIFADDR_IN6");
226723f86d2Sreyk 		break;
22797f33f1dSdv 	case IMSG_VMDOP_PRIV_GET_ADDR:
22897f33f1dSdv 		IMSG_SIZE_CHECK(imsg, &vareq);
22997f33f1dSdv 		memcpy(&vareq, imsg->data, sizeof(vareq));
23097f33f1dSdv 
23197f33f1dSdv 		varesult.var_vmid = vareq.var_vmid;
23297f33f1dSdv 		varesult.var_nic_idx = vareq.var_nic_idx;
23397f33f1dSdv 
23453027660Sclaudio 		ifd = imsg_get_fd(imsg);
23597f33f1dSdv 		/* resolve lladdr for the tap(4) and send back to parent */
23653027660Sclaudio 		if (ioctl(ifd, SIOCGIFADDR, &varesult.var_addr) != 0)
23797f33f1dSdv 			log_warn("SIOCGIFADDR");
23897f33f1dSdv 		else
23997f33f1dSdv 			proc_compose_imsg(ps, PROC_PARENT, -1,
24097f33f1dSdv 			    IMSG_VMDOP_PRIV_GET_ADDR_RESPONSE, imsg->hdr.peerid,
24197f33f1dSdv 			    -1, &varesult, sizeof(varesult));
24253027660Sclaudio 		close(ifd);
24397f33f1dSdv 		break;
244c48cfcf4Sreyk 	case IMSG_VMDOP_CONFIG:
245c48cfcf4Sreyk 		config_getconfig(env, imsg);
246c48cfcf4Sreyk 		break;
247c48cfcf4Sreyk 	case IMSG_CTL_RESET:
248c48cfcf4Sreyk 		config_getreset(env, imsg);
249c48cfcf4Sreyk 		break;
2505921535cSreyk 	default:
2515921535cSreyk 		return (-1);
2525921535cSreyk 	}
2535921535cSreyk 
2545921535cSreyk 	return (0);
2555921535cSreyk }
2565921535cSreyk 
2575921535cSreyk int
2585921535cSreyk priv_getiftype(char *ifname, char *type, unsigned int *unitptr)
2595921535cSreyk {
2605921535cSreyk 	const char	*errstr;
2615921535cSreyk 	size_t		 span;
2625921535cSreyk 	unsigned int	 unit;
2635921535cSreyk 
2645921535cSreyk 	/* Extract the name part */
2655921535cSreyk 	span = strcspn(ifname, "0123456789");
2665921535cSreyk 	if (span == 0 || span >= strlen(ifname) || span >= (IF_NAMESIZE - 1))
2675921535cSreyk 		return (-1);
2685921535cSreyk 	memcpy(type, ifname, span);
2695921535cSreyk 	type[span] = 0;
2705921535cSreyk 
2715921535cSreyk 	/* Now parse the unit (we don't strictly validate the format here) */
2725921535cSreyk 	unit = strtonum(ifname + span, 0, UINT_MAX, &errstr);
2735921535cSreyk 	if (errstr != NULL)
2745921535cSreyk 		return (-1);
2755921535cSreyk 	if (unitptr != NULL)
2765921535cSreyk 		*unitptr = unit;
2775921535cSreyk 
2785921535cSreyk 	return (0);
2795921535cSreyk }
2805921535cSreyk 
2815921535cSreyk int
2825921535cSreyk priv_findname(const char *name, const char **names)
2835921535cSreyk {
2845921535cSreyk 	unsigned int	 i;
2855921535cSreyk 
2865921535cSreyk 	for (i = 0; names[i] != NULL; i++) {
2875921535cSreyk 		if (strcmp(name, names[i]) == 0)
2885921535cSreyk 			return (0);
2895921535cSreyk 	}
2905921535cSreyk 
2915921535cSreyk 	return (-1);
2925921535cSreyk }
2935921535cSreyk 
2942b519c1fSreyk int
2952b519c1fSreyk priv_validgroup(const char *name)
2962b519c1fSreyk {
2971c6718c9Smillert 	const size_t len = strnlen(name, IF_NAMESIZE);
2981c6718c9Smillert 
2991c6718c9Smillert 	if (len == IF_NAMESIZE)
3002b519c1fSreyk 		return (-1);
3012b519c1fSreyk 	/* Group can not end with a digit */
3021c6718c9Smillert 	if (len > 0 && isdigit((unsigned char)name[len - 1]))
3032b519c1fSreyk 		return (-1);
3042b519c1fSreyk 	return (0);
3052b519c1fSreyk }
3062b519c1fSreyk 
3075921535cSreyk /*
308e330d69cSmlarkin  * Called from the Parent process to setup vm interface(s)
309e330d69cSmlarkin  * - ensure the interface has the description set (tracking purposes)
310e330d69cSmlarkin  * - if interface is to be attached to a switch, attach it
311e330d69cSmlarkin  * - check if rdomain is set on interface and switch
312e330d69cSmlarkin  *   - if interface only or both, use interface rdomain
313e330d69cSmlarkin  *   - if switch only, use switch rdomain
314e330d69cSmlarkin  * - check if group is set on interface and switch
315e330d69cSmlarkin  *   - if interface, add it
316e330d69cSmlarkin  *   - if switch, add it
317e330d69cSmlarkin  * - ensure the interface is up/down
318e330d69cSmlarkin  * - if local interface, set address
3195921535cSreyk  */
3205921535cSreyk int
3215921535cSreyk vm_priv_ifconfig(struct privsep *ps, struct vmd_vm *vm)
3225921535cSreyk {
323723f86d2Sreyk 	char			 name[64];
324c48cfcf4Sreyk 	struct vmd		*env = ps->ps_env;
325ea9c30d9Sedd 	struct vm_create_params	*vcp = &vm->vm_params.vmc_params;
326789e0822Sreyk 	struct vmd_if		*vif;
327789e0822Sreyk 	struct vmd_switch	*vsw;
3285921535cSreyk 	unsigned int		 i;
329789e0822Sreyk 	struct vmop_ifreq	 vfr, vfbr;
330470adcf5Sreyk 	struct sockaddr_in	*sin4;
331723f86d2Sreyk 	struct sockaddr_in6	*sin6;
3325921535cSreyk 
333d489aa7eSdv 	for (i = 0; i < VM_MAX_NICS_PER_VM; i++) {
334789e0822Sreyk 		vif = &vm->vm_ifs[i];
335789e0822Sreyk 
336789e0822Sreyk 		if (vif->vif_name == NULL)
3375921535cSreyk 			break;
3385921535cSreyk 
339723f86d2Sreyk 		memset(&vfr, 0, sizeof(vfr));
340789e0822Sreyk 		if (strlcpy(vfr.vfr_name, vif->vif_name,
3415921535cSreyk 		    sizeof(vfr.vfr_name)) >= sizeof(vfr.vfr_name))
3425921535cSreyk 			return (-1);
3435921535cSreyk 
3445921535cSreyk 		/* Description can be truncated */
3455921535cSreyk 		(void)snprintf(vfr.vfr_value, sizeof(vfr.vfr_value),
3465921535cSreyk 		    "vm%u-if%u-%s", vm->vm_vmid, i, vcp->vcp_name);
3475921535cSreyk 
348789e0822Sreyk 		log_debug("%s: interface %s description %s", __func__,
349789e0822Sreyk 		    vfr.vfr_name, vfr.vfr_value);
350789e0822Sreyk 
3515921535cSreyk 		proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFDESCR,
3525921535cSreyk 		    &vfr, sizeof(vfr));
3535921535cSreyk 
354e330d69cSmlarkin 		/* set default rdomain */
355e330d69cSmlarkin 		vfr.vfr_id = getrtable();
356e330d69cSmlarkin 
357e330d69cSmlarkin 		vsw = switch_getbyname(vif->vif_switch);
358e330d69cSmlarkin 
359e330d69cSmlarkin 		/* Check if switch should exist */
360e330d69cSmlarkin 		if (vsw == NULL && vif->vif_switch != NULL)
361e330d69cSmlarkin 			log_warnx("switch \"%s\" not found", vif->vif_switch);
362e330d69cSmlarkin 
363e330d69cSmlarkin 		/* Add interface to switch and set proper rdomain */
364e330d69cSmlarkin 		if (vsw != NULL) {
365f418e70cSreyk 			memset(&vfbr, 0, sizeof(vfbr));
366f418e70cSreyk 
367789e0822Sreyk 			if (strlcpy(vfbr.vfr_name, vsw->sw_ifname,
368789e0822Sreyk 			    sizeof(vfbr.vfr_name)) >= sizeof(vfbr.vfr_name))
369789e0822Sreyk 				return (-1);
370789e0822Sreyk 			if (strlcpy(vfbr.vfr_value, vif->vif_name,
371789e0822Sreyk 			    sizeof(vfbr.vfr_value)) >= sizeof(vfbr.vfr_value))
372789e0822Sreyk 				return (-1);
373789e0822Sreyk 
374e330d69cSmlarkin 			log_debug("%s: switch \"%s\" interface %s add %s",
375e330d69cSmlarkin 			    __func__, vsw->sw_name, vfbr.vfr_name,
376e330d69cSmlarkin 			    vfbr.vfr_value);
377789e0822Sreyk 
378789e0822Sreyk 			proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFADD,
379789e0822Sreyk 			    &vfbr, sizeof(vfbr));
380e330d69cSmlarkin 
381e330d69cSmlarkin 			/* Check rdomain properties */
382e330d69cSmlarkin 			if (vif->vif_flags & VMIFF_RDOMAIN)
383e330d69cSmlarkin 				vfr.vfr_id = vif->vif_rdomain;
384e330d69cSmlarkin 			else if (vsw->sw_flags & VMIFF_RDOMAIN)
385e330d69cSmlarkin 				vfr.vfr_id = vsw->sw_rdomain;
386e330d69cSmlarkin 		} else {
387e330d69cSmlarkin 			/* No switch to attach case */
388e330d69cSmlarkin 			if (vif->vif_flags & VMIFF_RDOMAIN)
389e330d69cSmlarkin 				vfr.vfr_id = vif->vif_rdomain;
390e330d69cSmlarkin 		}
391e330d69cSmlarkin 
392e330d69cSmlarkin 		/* Set rdomain on interface */
393e330d69cSmlarkin 		if (vfr.vfr_id != 0)
394e330d69cSmlarkin 			log_debug("%s: interface %s rdomain %u", __func__,
395e330d69cSmlarkin 			    vfr.vfr_name, vfr.vfr_id);
396e330d69cSmlarkin 
397e330d69cSmlarkin 		proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFRDOMAIN,
398e330d69cSmlarkin 		    &vfr, sizeof(vfr));
399789e0822Sreyk 
4004629ffa0Sreyk 		/* First group is defined per-interface */
4014629ffa0Sreyk 		if (vif->vif_group) {
4024629ffa0Sreyk 			if (strlcpy(vfr.vfr_value, vif->vif_group,
4034629ffa0Sreyk 			    sizeof(vfr.vfr_value)) >= sizeof(vfr.vfr_value))
4044629ffa0Sreyk 				return (-1);
4054629ffa0Sreyk 
4064629ffa0Sreyk 			log_debug("%s: interface %s group %s", __func__,
4074629ffa0Sreyk 			    vfr.vfr_name, vfr.vfr_value);
4084629ffa0Sreyk 
4094629ffa0Sreyk 			proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFGROUP,
4104629ffa0Sreyk 			    &vfr, sizeof(vfr));
4114629ffa0Sreyk 		}
4124629ffa0Sreyk 
4134629ffa0Sreyk 		/* The second group is defined per-switch */
4144629ffa0Sreyk 		if (vsw != NULL && vsw->sw_group != NULL) {
4154629ffa0Sreyk 			if (strlcpy(vfr.vfr_value, vsw->sw_group,
4164629ffa0Sreyk 			    sizeof(vfr.vfr_value)) >= sizeof(vfr.vfr_value))
4174629ffa0Sreyk 				return (-1);
4184629ffa0Sreyk 
419e330d69cSmlarkin 			log_debug("%s: interface %s group %s switch \"%s\"",
4204629ffa0Sreyk 			    __func__, vfr.vfr_name, vfr.vfr_value,
4214629ffa0Sreyk 			    vsw->sw_name);
4224629ffa0Sreyk 
4234629ffa0Sreyk 			proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFGROUP,
4244629ffa0Sreyk 			    &vfr, sizeof(vfr));
4254629ffa0Sreyk 		}
4264629ffa0Sreyk 
427789e0822Sreyk 		/* Set the new interface status to up or down */
4282b2a5f0dSreyk 		proc_compose(ps, PROC_PRIV, (vif->vif_flags & VMIFF_UP) ?
429789e0822Sreyk 		    IMSG_VMDOP_PRIV_IFUP : IMSG_VMDOP_PRIV_IFDOWN,
430789e0822Sreyk 		    &vfr, sizeof(vfr));
431470adcf5Sreyk 
432e330d69cSmlarkin 		/* Set interface address if it is a local interface */
433470adcf5Sreyk 		if (vm->vm_params.vmc_ifflags[i] & VMIFF_LOCAL) {
434723f86d2Sreyk 			memset(&vfr.vfr_mask, 0, sizeof(vfr.vfr_mask));
435723f86d2Sreyk 			memset(&vfr.vfr_addr, 0, sizeof(vfr.vfr_addr));
436723f86d2Sreyk 
437723f86d2Sreyk 			/* local IPv4 address with a /31 mask */
438723f86d2Sreyk 			sin4 = (struct sockaddr_in *)&vfr.vfr_mask;
439470adcf5Sreyk 			sin4->sin_family = AF_INET;
440470adcf5Sreyk 			sin4->sin_len = sizeof(*sin4);
441470adcf5Sreyk 			sin4->sin_addr.s_addr = htonl(0xfffffffe);
442470adcf5Sreyk 
443723f86d2Sreyk 			sin4 = (struct sockaddr_in *)&vfr.vfr_addr;
444470adcf5Sreyk 			sin4->sin_family = AF_INET;
445470adcf5Sreyk 			sin4->sin_len = sizeof(*sin4);
446470adcf5Sreyk 			if ((sin4->sin_addr.s_addr =
4472272e586Sdv 			    vm_priv_addr(&env->vmd_cfg.cfg_localprefix,
448c48cfcf4Sreyk 			    vm->vm_vmid, i, 0)) == 0)
449470adcf5Sreyk 				return (-1);
450470adcf5Sreyk 
451723f86d2Sreyk 			inet_ntop(AF_INET, &sin4->sin_addr,
452723f86d2Sreyk 			    name, sizeof(name));
453470adcf5Sreyk 			log_debug("%s: interface %s address %s/31",
454723f86d2Sreyk 			    __func__, vfr.vfr_name, name);
455470adcf5Sreyk 
456470adcf5Sreyk 			proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFADDR,
457470adcf5Sreyk 			    &vfr, sizeof(vfr));
458470adcf5Sreyk 		}
459723f86d2Sreyk 		if ((vm->vm_params.vmc_ifflags[i] & VMIFF_LOCAL) &&
460723f86d2Sreyk 		    (env->vmd_cfg.cfg_flags & VMD_CFG_INET6)) {
461723f86d2Sreyk 			memset(&vfr.vfr_mask, 0, sizeof(vfr.vfr_mask));
462723f86d2Sreyk 			memset(&vfr.vfr_addr, 0, sizeof(vfr.vfr_addr));
463723f86d2Sreyk 
464723f86d2Sreyk 			/* local IPv6 address with a /96 mask */
465723f86d2Sreyk 			sin6 = ss2sin6(&vfr.vfr_mask);
466723f86d2Sreyk 			sin6->sin6_family = AF_INET6;
467723f86d2Sreyk 			sin6->sin6_len = sizeof(*sin6);
468723f86d2Sreyk 			memset(&sin6->sin6_addr.s6_addr[0], 0xff, 12);
469723f86d2Sreyk 			memset(&sin6->sin6_addr.s6_addr[12], 0, 4);
470723f86d2Sreyk 
471723f86d2Sreyk 			sin6 = ss2sin6(&vfr.vfr_addr);
472723f86d2Sreyk 			sin6->sin6_family = AF_INET6;
473723f86d2Sreyk 			sin6->sin6_len = sizeof(*sin6);
4742272e586Sdv 			if (vm_priv_addr6(&env->vmd_cfg.cfg_localprefix,
475723f86d2Sreyk 			    vm->vm_vmid, i, 0, &sin6->sin6_addr) == -1)
476723f86d2Sreyk 				return (-1);
477723f86d2Sreyk 
478723f86d2Sreyk 			inet_ntop(AF_INET6, &sin6->sin6_addr,
479723f86d2Sreyk 			    name, sizeof(name));
480723f86d2Sreyk 			log_debug("%s: interface %s address %s/96",
481723f86d2Sreyk 			    __func__, vfr.vfr_name, name);
482723f86d2Sreyk 
483723f86d2Sreyk 			proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFADDR6,
484723f86d2Sreyk 			    &vfr, sizeof(vfr));
485723f86d2Sreyk 		}
4865921535cSreyk 	}
4875921535cSreyk 
4885921535cSreyk 	return (0);
4895921535cSreyk }
490789e0822Sreyk 
491e330d69cSmlarkin /*
492e330d69cSmlarkin  * Called from the Parent process to setup underlying switch interface
493e330d69cSmlarkin  * - ensure the interface exists
494e330d69cSmlarkin  * - ensure the interface has the correct rdomain set
495e330d69cSmlarkin  * - ensure the interface has the description set (tracking purposes)
496e330d69cSmlarkin  * - ensure the interface is up/down
497e330d69cSmlarkin  */
498789e0822Sreyk int
499789e0822Sreyk vm_priv_brconfig(struct privsep *ps, struct vmd_switch *vsw)
500789e0822Sreyk {
501789e0822Sreyk 	struct vmop_ifreq	 vfr;
502789e0822Sreyk 
503f418e70cSreyk 	memset(&vfr, 0, sizeof(vfr));
504f418e70cSreyk 
505789e0822Sreyk 	if (strlcpy(vfr.vfr_name, vsw->sw_ifname,
506789e0822Sreyk 	    sizeof(vfr.vfr_name)) >= sizeof(vfr.vfr_name))
507789e0822Sreyk 		return (-1);
508789e0822Sreyk 
50940d1acf5Ssthen 	/* ensure bridge exists */
510a123de80Smlarkin 	proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFEXISTS,
511789e0822Sreyk 	    &vfr, sizeof(vfr));
512789e0822Sreyk 
513e08715daSreyk 	/* Use the configured rdomain or get it from the process */
514f418e70cSreyk 	if (vsw->sw_flags & VMIFF_RDOMAIN)
515e08715daSreyk 		vfr.vfr_id = vsw->sw_rdomain;
516e08715daSreyk 	else
517e08715daSreyk 		vfr.vfr_id = getrtable();
518e08715daSreyk 	if (vfr.vfr_id != 0)
519e08715daSreyk 		log_debug("%s: interface %s rdomain %u", __func__,
520e08715daSreyk 		    vfr.vfr_name, vfr.vfr_id);
521e08715daSreyk 
52240d1acf5Ssthen 	/* ensure bridge has the correct rdomain */
523f418e70cSreyk 	proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFRDOMAIN,
524f418e70cSreyk 	    &vfr, sizeof(vfr));
525f418e70cSreyk 
526789e0822Sreyk 	/* Description can be truncated */
527789e0822Sreyk 	(void)snprintf(vfr.vfr_value, sizeof(vfr.vfr_value),
528789e0822Sreyk 	    "switch%u-%s", vsw->sw_id, vsw->sw_name);
529789e0822Sreyk 
530789e0822Sreyk 	log_debug("%s: interface %s description %s", __func__,
531789e0822Sreyk 	    vfr.vfr_name, vfr.vfr_value);
532789e0822Sreyk 
533789e0822Sreyk 	proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFDESCR,
534789e0822Sreyk 	    &vfr, sizeof(vfr));
535789e0822Sreyk 
536789e0822Sreyk 	/* Set the new interface status to up or down */
5372b2a5f0dSreyk 	proc_compose(ps, PROC_PRIV, (vsw->sw_flags & VMIFF_UP) ?
538789e0822Sreyk 	    IMSG_VMDOP_PRIV_IFUP : IMSG_VMDOP_PRIV_IFDOWN,
539789e0822Sreyk 	    &vfr, sizeof(vfr));
540789e0822Sreyk 
541ea9c30d9Sedd 	vsw->sw_running = 1;
542789e0822Sreyk 	return (0);
543789e0822Sreyk }
544470adcf5Sreyk 
545470adcf5Sreyk uint32_t
5462272e586Sdv vm_priv_addr(struct local_prefix *p, uint32_t vmid, int idx, int isvm)
547470adcf5Sreyk {
5482272e586Sdv 	in_addr_t		 addr;
549470adcf5Sreyk 
5502272e586Sdv 	/* Encode the VM ID as a per-VM subnet range N, 100.64.N.0/24. */
551470adcf5Sreyk 	addr = vmid << 8;
552470adcf5Sreyk 
553470adcf5Sreyk 	/*
5542272e586Sdv 	 * Assign a /31 subnet M per VM interface, 100.64.N.M/31.
555470adcf5Sreyk 	 * Each subnet contains exactly two IP addresses; skip the
556470adcf5Sreyk 	 * first subnet to avoid a gateway address ending with .0.
557470adcf5Sreyk 	 */
558470adcf5Sreyk 	addr |= (idx + 1) * 2;
559470adcf5Sreyk 
5602272e586Sdv 	/* Use the first address for the gateway, the second for the VM. */
561470adcf5Sreyk 	if (isvm)
562470adcf5Sreyk 		addr++;
563470adcf5Sreyk 
5642272e586Sdv 	/* Convert to network byte order and add the prefix. */
5652272e586Sdv 	addr = htonl(addr) | p->lp_in.s_addr;
566470adcf5Sreyk 
567470adcf5Sreyk 	/*
568470adcf5Sreyk 	 * Validate the results:
569470adcf5Sreyk 	 * - the address should not exceed the prefix (eg. VM ID to high).
570470adcf5Sreyk 	 * - up to 126 interfaces can be encoded per VM.
571470adcf5Sreyk 	 */
5722272e586Sdv 	if (p->lp_in.s_addr != (addr & p->lp_mask.s_addr) || idx >= 0x7f) {
573470adcf5Sreyk 		log_warnx("%s: dhcp address range exceeded,"
574470adcf5Sreyk 		    " vm id %u interface %d", __func__, vmid, idx);
575470adcf5Sreyk 		return (0);
576470adcf5Sreyk 	}
577470adcf5Sreyk 
578470adcf5Sreyk 	return (addr);
579470adcf5Sreyk }
580723f86d2Sreyk 
581723f86d2Sreyk int
5822272e586Sdv vm_priv_addr6(struct local_prefix *p, uint32_t vmid, int idx, int isvm,
5832272e586Sdv     struct in6_addr *out)
584723f86d2Sreyk {
5852272e586Sdv 	struct in6_addr		 addr;
5862272e586Sdv 	in_addr_t		 addr4;
587723f86d2Sreyk 
5882272e586Sdv 	/* Start with the IPv6 prefix. */
5892272e586Sdv 	memcpy(&addr, &p->lp_in6, sizeof(addr));
590723f86d2Sreyk 
5912272e586Sdv 	/* Encode the VM IPv4 address as subnet, fd00::NN:NN:0:0/96. */
5922272e586Sdv 	if ((addr4 = vm_priv_addr(p, vmid, idx, 1)) == 0)
593723f86d2Sreyk 		return (0);
594723f86d2Sreyk 	memcpy(&addr.s6_addr[8], &addr4, sizeof(addr4));
595723f86d2Sreyk 
596723f86d2Sreyk 	/*
5972272e586Sdv 	 * Set the last octet to 1 (host) or 2 (VM).
598723f86d2Sreyk 	 * The latter is currently not used inside vmd as we don't
599723f86d2Sreyk 	 * answer rtsol requests ourselves.
600723f86d2Sreyk 	 */
601723f86d2Sreyk 	if (!isvm)
602723f86d2Sreyk 		addr.s6_addr[15] = 1;
603723f86d2Sreyk 	else
604723f86d2Sreyk 		addr.s6_addr[15] = 2;
605723f86d2Sreyk 
6062272e586Sdv 	memcpy(out, &addr, sizeof(*out));
607723f86d2Sreyk 
608723f86d2Sreyk 	return (0);
609723f86d2Sreyk }
610