diff options
author | Reyk Floeter <reyk@cvs.openbsd.org> | 2017-04-19 15:38:33 +0000 |
---|---|---|
committer | Reyk Floeter <reyk@cvs.openbsd.org> | 2017-04-19 15:38:33 +0000 |
commit | 923ce6da9a97e870813201b5f5f266e32187d7ec (patch) | |
tree | 9492bf8f36f808d081b16405bf4a011e6d3726d0 /usr.sbin/vmd/priv.c | |
parent | 3c74bbd95f27b43502ef7e7923c49587fff96320 (diff) |
Add support for dynamic "NAT" interfaces (-L/local interface).
When a local interface is configured, vmd configures a /31 address on
the tap(4) interface of the host and provides another IP in the same
subnet via DHCP (BOOTP) to the VM. vmd runs an internal BOOTP server
that replies with IP, gateway, and DNS addresses to the VM. The
built-in server only ever responds to the VM on the inside and cannot
leak its DHCP responses to the outside.
Thanks to Uwe Werler, Josh Grosse, and some others for testing!
OK deraadt@
Diffstat (limited to 'usr.sbin/vmd/priv.c')
-rw-r--r-- | usr.sbin/vmd/priv.c | 90 |
1 files changed, 89 insertions, 1 deletions
diff --git a/usr.sbin/vmd/priv.c b/usr.sbin/vmd/priv.c index 2b99a246259..c0f1c36fd25 100644 --- a/usr.sbin/vmd/priv.c +++ b/usr.sbin/vmd/priv.c @@ -1,4 +1,4 @@ -/* $OpenBSD: priv.c,v 1.6 2017/03/02 07:33:37 reyk Exp $ */ +/* $OpenBSD: priv.c,v 1.7 2017/04/19 15:38:32 reyk Exp $ */ /* * Copyright (c) 2016 Reyk Floeter <reyk@openbsd.org> @@ -29,6 +29,8 @@ #include <netinet/if_ether.h> #include <net/if_bridge.h> +#include <arpa/inet.h> + #include <errno.h> #include <event.h> #include <fcntl.h> @@ -80,6 +82,7 @@ priv_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) struct ifreq ifr; struct ifbreq ifbr; struct ifgroupreq ifgr; + struct ifaliasreq ifra; char type[IF_NAMESIZE]; switch (imsg->hdr.type) { @@ -89,6 +92,7 @@ priv_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) case IMSG_VMDOP_PRIV_IFUP: case IMSG_VMDOP_PRIV_IFDOWN: case IMSG_VMDOP_PRIV_IFGROUP: + case IMSG_VMDOP_PRIV_IFADDR: IMSG_SIZE_CHECK(imsg, &vfr); memcpy(&vfr, imsg->data, sizeof(vfr)); @@ -160,6 +164,25 @@ priv_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) errno != EEXIST) log_warn("SIOCAIFGROUP"); break; + case IMSG_VMDOP_PRIV_IFADDR: + memset(&ifra, 0, sizeof(ifra)); + + /* Set the interface address */ + strlcpy(ifra.ifra_name, vfr.vfr_name, sizeof(ifra.ifra_name)); + + memcpy(&ifra.ifra_addr, &vfr.vfr_ifra.ifra_addr, + sizeof(ifra.ifra_addr)); + ifra.ifra_addr.sa_family = AF_INET; + ifra.ifra_addr.sa_len = sizeof(struct sockaddr_in); + + memcpy(&ifra.ifra_mask, &vfr.vfr_ifra.ifra_mask, + sizeof(ifra.ifra_mask)); + ifra.ifra_mask.sa_family = AF_INET; + ifra.ifra_mask.sa_len = sizeof(struct sockaddr_in); + + if (ioctl(env->vmd_fd, SIOCAIFADDR, &ifra) < 0) + log_warn("SIOCAIFADDR"); + break; default: return (-1); } @@ -227,6 +250,7 @@ vm_priv_ifconfig(struct privsep *ps, struct vmd_vm *vm) struct vmd_switch *vsw; unsigned int i; struct vmop_ifreq vfr, vfbr; + struct sockaddr_in *sin4; for (i = 0; i < VMM_MAX_NICS_PER_VM; i++) { vif = &vm->vm_ifs[i]; @@ -298,6 +322,27 @@ vm_priv_ifconfig(struct privsep *ps, struct vmd_vm *vm) proc_compose(ps, PROC_PRIV, (vif->vif_flags & VMIFF_UP) ? IMSG_VMDOP_PRIV_IFUP : IMSG_VMDOP_PRIV_IFDOWN, &vfr, sizeof(vfr)); + + if (vm->vm_params.vmc_ifflags[i] & VMIFF_LOCAL) { + sin4 = (struct sockaddr_in *)&vfr.vfr_ifra.ifra_mask; + sin4->sin_family = AF_INET; + sin4->sin_len = sizeof(*sin4); + sin4->sin_addr.s_addr = htonl(0xfffffffe); + + sin4 = (struct sockaddr_in *)&vfr.vfr_ifra.ifra_addr; + sin4->sin_family = AF_INET; + sin4->sin_len = sizeof(*sin4); + if ((sin4->sin_addr.s_addr = + vm_priv_addr(vm->vm_vmid, i, 0)) == 0) + return (-1); + + log_debug("%s: interface %s address %s/31", + __func__, vfr.vfr_name, + inet_ntoa(sin4->sin_addr)); + + proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFADDR, + &vfr, sizeof(vfr)); + } } return (0); @@ -346,3 +391,46 @@ vm_priv_brconfig(struct privsep *ps, struct vmd_switch *vsw) vsw->sw_running = 1; return (0); } + +uint32_t +vm_priv_addr(uint32_t vmid, int idx, int isvm) +{ + in_addr_t prefix, mask, addr; + + /* + * 1. Set the address prefix and mask, 100.64.0.0/10 by default. + * XXX make the global prefix configurable. + */ + prefix = inet_addr(VMD_DHCP_PREFIX); + mask = prefixlen2mask(VMD_DHCP_PREFIXLEN); + + /* 2. Encode the VM ID as a per-VM subnet range N, 10.64.N.0/24. */ + addr = vmid << 8; + + /* + * 3. Assign a /31 subnet M per VM interface, 10.64.N.M/31. + * Each subnet contains exactly two IP addresses; skip the + * first subnet to avoid a gateway address ending with .0. + */ + addr |= (idx + 1) * 2; + + /* 4. Use the first address for the gateway, the second for the VM. */ + if (isvm) + addr++; + + /* 5. Convert to network byte order and add the prefix. */ + addr = htonl(addr) | prefix; + + /* + * Validate the results: + * - the address should not exceed the prefix (eg. VM ID to high). + * - up to 126 interfaces can be encoded per VM. + */ + if (prefix != (addr & mask) || idx >= 0x7f) { + log_warnx("%s: dhcp address range exceeded," + " vm id %u interface %d", __func__, vmid, idx); + return (0); + } + + return (addr); +} |