--
> On 25 Oct 2019, at 21:53, Mike Larkin <[email protected]> wrote:
>
> On Fri, Oct 25, 2019 at 07:47:35PM +0000, Reyk Floeter wrote:
>>> On Fri, Oct 25, 2019 at 12:27:25PM -0700, Mike Larkin wrote:
>>> On Fri, Oct 25, 2019 at 06:15:59PM +0000, Reyk Floeter wrote:
>>>> Hi,
>>>>
>>>> the attached diff is rather large and implements two things for vmd:
>>>>
>>>> 1) Allow to configure static IP address/gateway pairs local interfaces.
>>>> 2) Skip statically configured interface names (eg. tap0) when
>>>> allocating dynamic interfaces.
>>>>
>>>> Example:
>>>> ---snip---
>>>> vm "foo" {
>>>> disable
>>>> local interface "tap0" {
>>>> address 192.168.0.10/24 192.168.0.1
>>>> }
>>>> local interface "tap1"
>>>> disk "/home/vm/foo.qcow2"
>>>> }
>>>>
>>>> vm "bar" {
>>>> local interface
>>>> disk "/home/vm/bar.qcow2"
>>>> }
>>>> ---snap---
>>>>
>>>>
>>>> 1) The VM "foo" has two interfaces: The first interface has a fixed
>>>> IPv4 address with 192.168.0.1/24 on the gateway and 192.168.0.10/24 on
>>>> the VM. 192.168.0.10/24 is assigned to the VM's first NIC via the
>>>> built-in DHCP server. The second VM gets a default 100.64.x.x/31 IP.
>>>
>>> I'm not sure the above description matches what I'm seeing in the vm.conf
>>> snippet above.
>>>
>>> What's "the gateway" here? Is this the host machine, or the actual
>>> gateway, perhaps on some other machine? Does this just allow me to specify
>>> the host-side tap(4) IP address for a corresponding given VM vio(4)
>>> interface?
>>>
>>
>> Ah, OK. I used the terms without explaining them:
>>
>> With local interfaces, vmd(8) uses two IPs per interface: one for the
>> tap(4) on the host, one for the vio(4) on the VM. It configures the
>> first one on the host and provides the second one via DHCP. The IP on
>> the host IP is the default "gateway" router for the VM.
>>
>
> Ah, I missed the fact that these are not "-i" style interfaces but rather
> *local* interfaces (eg, "-L" style).
>
>> The address syntax is currently reversed:
>> address "address/prefix" "gateway"
>> Maybe I should change it to
>> address "gateway" "address/prefix"
>> or
>> address "address/prefix" gateway "gateway"
>
> I like the last one, but I probably won't be a heavy user of this ...
I think the last is most clear, will be a heavy user of this. :))
The IPs which are assigned this way will be mapped to the vlan/interface with
the same subnet on the host to get out?
Or does it still require a assigned interface in vm.conf?
Very cool Reyk! Happy you managed to spend some time on this.
Mischa
>
> -ml
>
>>
>> I also wonder if we could technically use a non-local IP address for
>> the gateway. I currently enforce that the prefix matches, but I don't
>> enforce that both addresses are in the same subnet.
>>
>> When using the default auto-generated 100.64.0.0/31 method, it uses
>> the first IP in the subnet as the gateway and the second IP for the
>> VM.
>>
>>> And did you mean "The second interface" there instead of the "The second
>>> VM"?
>>> (Although I think the description fits for "The second VM" also...)
>>>
>>
>> Yes, both, the second interface is correct as well.
>>
>>> I think the idea is sound. As long as we don't end up adding extra command
>>> line args to vmctl to manually configure this, which it doesn't appear we
>>> are
>>> doing here. :)
>>>
>>
>> I don't want to add it to vmctl either.
>>
>>> I didn't read the diff in great detail, I'll wait until you say you have a
>>> final version.
>>>
>>
>> OK, thanks.
>>
>> Reyk
>>
>>> -ml
>>>
>>>> This idea came up when I talked with Mischa at EuroBSDCon about
>>>> OpenBSDAms: instead of using L2 and external static dhcpd for all VMs,
>>>> it could be a solution to use L3 and to avoid bridge(4) and dhcpd(8).
>>>> But it would need a way to serve static IPs via the internal dhcp
>>>> server. Using L3 with vmd is better with performance, routing, PF,
>>>> etc., but has the drawback that it wastes a subnet and gateway IP per
>>>> VM (maybe rdomains or other tricks could help here, but this is a
>>>> problem for later).
>>>>
>>>> 2) The VM "foo" uses two static interface names, tap0 and tap1, and
>>>> the VM "bar" uses a dynamic interface name (tapX). Without this diff,
>>>> vmd would most certainly use tap0 for bar's interface because foo is
>>>> disabled and not started before bar. With the diff, the first
>>>> interface of bar will be tap2 or higher.
>>>> The problem was just reported by kn@. I mixed both things into
>>>> one diff because I was working on 1) when kn@ reported it. There are
>>>> other ways to implement 2) but solving both issues in a similar way
>>>> made more sense.
>>>>
>>>> This is not the final diff. I still have to clean it up, get
>>>> feedback, think a little bit about it, and split it into smaller parts
>>>> for review. I wanted to share the big picture.
>>>>
>>>> As a side node, I implemented the lookup with sorted tables because it
>>>> is the most efficient way to do it, but maybe a simple linear lookup
>>>> (iterating over all the VMs and all the interfaces all the time) would
>>>> be good enough. But the current approach has benefits - if I did it
>>>> right ;)
>>>>
>>>> Thoughts?
>>>>
>>>> Reyk
>>>>
>>>> Index: usr.sbin/vmd/dhcp.c
>>>> ===================================================================
>>>> RCS file: /cvs/src/usr.sbin/vmd/dhcp.c,v
>>>> retrieving revision 1.8
>>>> diff -u -p -u -p -r1.8 dhcp.c
>>>> --- usr.sbin/vmd/dhcp.c 27 Dec 2018 19:51:30 -0000 1.8
>>>> +++ usr.sbin/vmd/dhcp.c 25 Oct 2019 18:11:05 -0000
>>>> @@ -119,8 +119,7 @@ dhcp_request(struct vionet_dev *dev, cha
>>>> }
>>>>
>>>> if ((client_addr.s_addr =
>>>> - vm_priv_addr(&env->vmd_cfg,
>>>> - dev->vm_vmid, dev->idx, 1)) == 0)
>>>> + vm_priv_addr(&env->vmd_cfg, dev->vm_vmid, dev->idx, 1, &mask)) ==
>>>> 0)
>>>> return (-1);
>>>> memcpy(&resp.yiaddr, &client_addr,
>>>> sizeof(client_addr));
>>>> @@ -129,7 +128,7 @@ dhcp_request(struct vionet_dev *dev, cha
>>>> ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT);
>>>>
>>>> if ((server_addr.s_addr = vm_priv_addr(&env->vmd_cfg, dev->vm_vmid,
>>>> - dev->idx, 0)) == 0)
>>>> + dev->idx, 0, &mask)) == 0)
>>>> return (-1);
>>>> memcpy(&resp.siaddr, &server_addr, sizeof(server_addr));
>>>> memcpy(&ss2sin(&pc.pc_src)->sin_addr, &server_addr,
>>>> Index: usr.sbin/vmd/parse.y
>>>> ===================================================================
>>>> RCS file: /cvs/src/usr.sbin/vmd/parse.y,v
>>>> retrieving revision 1.52
>>>> diff -u -p -u -p -r1.52 parse.y
>>>> --- usr.sbin/vmd/parse.y 14 May 2019 06:05:45 -0000 1.52
>>>> +++ usr.sbin/vmd/parse.y 25 Oct 2019 18:11:05 -0000
>>>> @@ -120,9 +120,9 @@ typedef struct {
>>>>
>>>>
>>>> %token INCLUDE ERROR
>>>> -%token ADD ALLOW BOOT CDROM DEVICE DISABLE DISK DOWN ENABLE FORMAT
>>>> GROUP
>>>> -%token INET6 INSTANCE INTERFACE LLADDR LOCAL LOCKED MEMORY NET NIFS
>>>> OWNER
>>>> -%token PATH PREFIX RDOMAIN SIZE SOCKET SWITCH UP VM VMID
>>>> +%token ADD ADDRESS ALLOW BOOT CDROM DEVICE DISABLE DISK DOWN ENABLE
>>>> FORMAT
>>>> +%token GROUP INET6 INSTANCE INTERFACE LLADDR LOCAL LOCKED MEMORY NET
>>>> NIFS
>>>> +%token OWNER PATH PREFIX RDOMAIN SIZE SOCKET SWITCH UP VM VMID
>>>> %token <v.number> NUMBER
>>>> %token <v.string> STRING
>>>> %type <v.lladdr> lladdr
>>>> @@ -413,6 +413,12 @@ vm_opts : disable {
>>>>
>>>> if ($1)
>>>> vmc.vmc_ifflags[i] |= VMIFF_LOCAL;
>>>> + else if (vmc.vmc_ifflags[i] &
>>>> + (VMIFF_ADDR4|VMIFF_ADDR6)) {
>>>> + yyerror("address on non-local interface");
>>>> + free($3);
>>>> + YYERROR;
>>>> + }
>>>> if ($3 != NULL) {
>>>> if (strcmp($3, "tap") != 0 &&
>>>> (priv_getiftype($3, type, NULL) == -1 ||
>>>> @@ -617,7 +623,53 @@ iface_opts_c : iface_opts_c iface_opts o
>>>> | iface_opts
>>>> ;
>>>>
>>>> -iface_opts : SWITCH string {
>>>> +iface_opts : ADDRESS STRING STRING {
>>>> + struct vmop_address *vma;
>>>> + unsigned int i = vcp_nnics;
>>>> + struct address addr, gw;
>>>> + char *gwp = NULL;
>>>> + int maxprefixlen = 0;
>>>> +
>>>> + /* Does the gateway have a /prefix syntax? */
>>>> + gwp = strrchr($3, '/');
>>>> +
>>>> + if (host($2, &addr) == -1 ||
>>>> + host($3, &gw) == -1 ||
>>>> + addr.ss.ss_family != gw.ss.ss_family) {
>>>> + yyerror("invalid address: %s %s", $2, $3);
>>>> + free($2);
>>>> + free($3);
>>>> + YYERROR;
>>>> + }
>>>> + free($2);
>>>> +
>>>> + if (gwp == NULL)
>>>> + gw.prefixlen = addr.prefixlen;
>>>> + else if (gw.prefixlen != addr.prefixlen) {
>>>> + yyerror("mismatched gateway prefix: %s", $3);
>>>> + free($3);
>>>> + YYERROR;
>>>> + }
>>>> + free($3);
>>>> +
>>>> + if (addr.ss.ss_family == AF_INET) {
>>>> + vmc.vmc_ifflags[i] |= VMIFF_ADDR4;
>>>> + vma = &vmc.vmc_ifaddr4[i];
>>>> + maxprefixlen = 127;
>>>> + } else {
>>>> + vmc.vmc_ifflags[i] |= VMIFF_ADDR6;
>>>> + vma = &vmc.vmc_ifaddr6[i];
>>>> + maxprefixlen = 31;
>>>> + }
>>>> + if (maxprefixlen && addr.prefixlen > maxprefixlen) {
>>>> + yyerror("address prefix larger than /%u");
>>>> + YYERROR;
>>>> + }
>>>> + memcpy(&vma->vma_addr, &addr.ss, sizeof(addr.ss));
>>>> + memcpy(&vma->vma_gw, &gw.ss, sizeof(gw.ss));
>>>> + vma->vma_prefixlen = addr.prefixlen;
>>>> + }
>>>> + | SWITCH string {
>>>> unsigned int i = vcp_nnics;
>>>>
>>>> /* No need to check if the switch exists */
>>>> @@ -763,6 +815,7 @@ lookup(char *s)
>>>> /* this has to be sorted always */
>>>> static const struct keywords keywords[] = {
>>>> { "add", ADD },
>>>> + { "address", ADDRESS },
>>>> { "allow", ALLOW },
>>>> { "boot", BOOT },
>>>> { "cdrom", CDROM },
>>>> Index: usr.sbin/vmd/priv.c
>>>> ===================================================================
>>>> RCS file: /cvs/src/usr.sbin/vmd/priv.c,v
>>>> retrieving revision 1.15
>>>> diff -u -p -u -p -r1.15 priv.c
>>>> --- usr.sbin/vmd/priv.c 28 Jun 2019 13:32:51 -0000 1.15
>>>> +++ usr.sbin/vmd/priv.c 25 Oct 2019 18:11:05 -0000
>>>> @@ -46,6 +46,12 @@
>>>> #include "proc.h"
>>>> #include "vmd.h"
>>>>
>>>> +static unsigned int *priv_ifunits;
>>>> +static size_t priv_nifunits;
>>>> +
>>>> +static struct vmd_ifconfig *priv_ifs;
>>>> +static size_t priv_nifs;
>>>> +
>>>> int priv_dispatch_parent(int, struct privsep_proc *, struct imsg *);
>>>> void priv_run(struct privsep *, struct privsep_proc *, void *);
>>>>
>>>> @@ -91,7 +97,9 @@ priv_dispatch_parent(int fd, struct priv
>>>> struct ifaliasreq ifra;
>>>> struct in6_aliasreq in6_ifra;
>>>> struct if_afreq ifar;
>>>> + struct vmd_ifconfig vifc;
>>>> char type[IF_NAMESIZE];
>>>> + int i;
>>>>
>>>> switch (imsg->hdr.type) {
>>>> case IMSG_VMDOP_PRIV_IFDESCR:
>>>> @@ -112,6 +120,8 @@ priv_dispatch_parent(int fd, struct priv
>>>> fatalx("%s: rejected priv operation on interface: %s",
>>>> __func__, vfr.vfr_name);
>>>> break;
>>>> + case IMSG_VMDOP_IF_REGISTER:
>>>> + case IMSG_VMDOP_IF_UNREGISTER:
>>>> case IMSG_VMDOP_CONFIG:
>>>> case IMSG_CTL_RESET:
>>>> break;
>>>> @@ -244,6 +254,18 @@ priv_dispatch_parent(int fd, struct priv
>>>> if (ioctl(env->vmd_fd6, SIOCAIFADDR_IN6, &in6_ifra) == -1)
>>>> log_warn("SIOCAIFADDR_IN6");
>>>> break;
>>>> + case IMSG_VMDOP_IF_REGISTER:
>>>> + IMSG_SIZE_CHECK(imsg, &vifc);
>>>> + memcpy(&vifc, imsg->data, sizeof(vifc));
>>>> + if (vm_priv_register(ps, &vifc) == -1)
>>>> + fatalx("%s: failed to register interface",
>>>> + __func__);
>>>> + break;
>>>> + case IMSG_VMDOP_IF_UNREGISTER:
>>>> + IMSG_SIZE_CHECK(imsg, &i);
>>>> + memcpy(&i, imsg->data, sizeof(i));
>>>> + vm_priv_unregister(ps, imsg->hdr.peerid, i);
>>>> + break;
>>>> case IMSG_VMDOP_CONFIG:
>>>> config_getconfig(env, imsg);
>>>> break;
>>>> @@ -328,8 +350,9 @@ vm_priv_ifconfig(struct privsep *ps, str
>>>> struct vmd_switch *vsw;
>>>> unsigned int i;
>>>> struct vmop_ifreq vfr, vfbr;
>>>> - struct sockaddr_in *sin4;
>>>> - struct sockaddr_in6 *sin6;
>>>> + struct sockaddr_in *sin4, *mask4;
>>>> + struct sockaddr_in6 *sin6, *mask6;
>>>> + uint8_t prefixlen;
>>>>
>>>> for (i = 0; i < VMM_MAX_NICS_PER_VM; i++) {
>>>> vif = &vm->vm_ifs[i];
>>>> @@ -435,24 +458,25 @@ vm_priv_ifconfig(struct privsep *ps, str
>>>> memset(&vfr.vfr_mask, 0, sizeof(vfr.vfr_mask));
>>>> memset(&vfr.vfr_addr, 0, sizeof(vfr.vfr_addr));
>>>>
>>>> - /* local IPv4 address with a /31 mask */
>>>> - sin4 = (struct sockaddr_in *)&vfr.vfr_mask;
>>>> - sin4->sin_family = AF_INET;
>>>> - sin4->sin_len = sizeof(*sin4);
>>>> - sin4->sin_addr.s_addr = htonl(0xfffffffe);
>>>> + /* local IPv4 address and netmask */
>>>> + mask4 = ss2sin(&vfr.vfr_mask);
>>>> + mask4->sin_family = AF_INET;
>>>> + mask4->sin_len = sizeof(*mask4);
>>>>
>>>> - sin4 = (struct sockaddr_in *)&vfr.vfr_addr;
>>>> + sin4 = ss2sin(&vfr.vfr_addr);
>>>> sin4->sin_family = AF_INET;
>>>> sin4->sin_len = sizeof(*sin4);
>>>> if ((sin4->sin_addr.s_addr =
>>>> vm_priv_addr(&env->vmd_cfg,
>>>> - vm->vm_vmid, i, 0)) == 0)
>>>> + vm->vm_vmid, i, 0, &mask4->sin_addr)) == 0)
>>>> return (-1);
>>>>
>>>> inet_ntop(AF_INET, &sin4->sin_addr,
>>>> name, sizeof(name));
>>>> - log_debug("%s: interface %s address %s/31",
>>>> - __func__, vfr.vfr_name, name);
>>>> + prefixlen = mask2prefixlen((struct sockaddr *)mask4);
>>>> +
>>>> + log_debug("%s: interface %s address %s/%u",
>>>> + __func__, vfr.vfr_name, name, prefixlen);
>>>>
>>>> proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFADDR,
>>>> &vfr, sizeof(vfr));
>>>> @@ -462,24 +486,24 @@ vm_priv_ifconfig(struct privsep *ps, str
>>>> memset(&vfr.vfr_mask, 0, sizeof(vfr.vfr_mask));
>>>> memset(&vfr.vfr_addr, 0, sizeof(vfr.vfr_addr));
>>>>
>>>> - /* local IPv6 address with a /96 mask */
>>>> - sin6 = ss2sin6(&vfr.vfr_mask);
>>>> - sin6->sin6_family = AF_INET6;
>>>> - sin6->sin6_len = sizeof(*sin6);
>>>> - memset(&sin6->sin6_addr.s6_addr[0], 0xff, 12);
>>>> - memset(&sin6->sin6_addr.s6_addr[12], 0, 4);
>>>> + /* local IPv6 address and netmask */
>>>> + mask6 = ss2sin6(&vfr.vfr_mask);
>>>> + mask6->sin6_family = AF_INET6;
>>>> + mask6->sin6_len = sizeof(*sin6);
>>>>
>>>> sin6 = ss2sin6(&vfr.vfr_addr);
>>>> sin6->sin6_family = AF_INET6;
>>>> sin6->sin6_len = sizeof(*sin6);
>>>> if (vm_priv_addr6(&env->vmd_cfg,
>>>> - vm->vm_vmid, i, 0, &sin6->sin6_addr) == -1)
>>>> + vm->vm_vmid, i, 0, &sin6->sin6_addr,
>>>> + &mask6->sin6_addr) == -1)
>>>> return (-1);
>>>>
>>>> inet_ntop(AF_INET6, &sin6->sin6_addr,
>>>> name, sizeof(name));
>>>> - log_debug("%s: interface %s address %s/96",
>>>> - __func__, vfr.vfr_name, name);
>>>> + prefixlen = mask2prefixlen6((struct sockaddr *)mask6);
>>>> + log_debug("%s: interface %s address %s/%u",
>>>> + __func__, vfr.vfr_name, name, prefixlen);
>>>>
>>>> proc_compose(ps, PROC_PRIV, IMSG_VMDOP_PRIV_IFADDR6,
>>>> &vfr, sizeof(vfr));
>>>> @@ -543,11 +567,196 @@ vm_priv_brconfig(struct privsep *ps, str
>>>> return (0);
>>>> }
>>>>
>>>> +static int
>>>> +priv_if_cmp(const void *a, const void *b)
>>>> +{
>>>> + const struct vmd_ifconfig *vifca = a;
>>>> + const struct vmd_ifconfig *vifcb = b;
>>>> +
>>>> + if (vifca->vifc_vmid != vifcb->vifc_vmid)
>>>> + return (vifca->vifc_vmid > vifcb->vifc_vmid ? 1 : -1);
>>>> + if (vifca->vifc_idx != vifcb->vifc_idx)
>>>> + return (vifca->vifc_idx > vifcb->vifc_idx ? 1 : -1);
>>>> +
>>>> + return (0);
>>>> +}
>>>> +
>>>> +static int
>>>> +priv_ifunit_cmp(const void *a, const void *b)
>>>> +{
>>>> + int ia = *(const unsigned int *)a;
>>>> + int ib = *(const unsigned int *)b;
>>>> +
>>>> + return ((int)ia - (int)ib);
>>>> +}
>>>> +
>>>> +unsigned int *
>>>> +vm_priv_byunit(unsigned int unit)
>>>> +{
>>>> + return (bsearch(&unit, priv_ifunits, priv_nifunits, sizeof(unit),
>>>> + priv_ifunit_cmp));
>>>> +}
>>>> +
>>>> +struct vmd_ifconfig *
>>>> +vm_priv_byid(uint32_t vmid, int idx)
>>>> +{
>>>> + struct vmd_ifconfig key;
>>>> +
>>>> + key.vifc_vmid = vmid;
>>>> + key.vifc_idx = idx;
>>>> + return (bsearch(&key, priv_ifs, priv_nifs, sizeof(key), priv_if_cmp));
>>>> +}
>>>> +
>>>> +/*
>>>> + * Called to register global interface configuration
>>>> + * - the associated VM id
>>>> + * - the relativ interface index of the VM
>>>> + * - the fixed tap(4) interface unit (optional)
>>>> + * - the fixed IP address (optional)
>>>> + */
>>>> +int
>>>> +vm_priv_register(struct privsep *ps, struct vmd_ifconfig *vifc)
>>>> +{
>>>> + struct vmd_ifconfig *ifc = NULL;
>>>> + unsigned int *ifu;
>>>> +
>>>> + /* Ignore interfaces that don't have any relevant configuration */
>>>> + if (vifc->vifc_flags == 0)
>>>> + return (0);
>>>> +
>>>> + if (vifc->vifc_vmid == UINT32_MAX) {
>>>> + log_warnx("VM id %u too large", vifc->vifc_unit);
>>>> + goto fail;
>>>> + }
>>>> +
>>>> + if (vm_priv_byid(vifc->vifc_vmid, vifc->vifc_idx) != NULL) {
>>>> + log_warnx("interface vm %u #%u registered twice",
>>>> + vifc->vifc_vmid, vifc->vifc_idx);
>>>> + goto fail;
>>>> + }
>>>> +
>>>> + /* Append new interface */
>>>> + if ((ifc = recallocarray(priv_ifs, priv_nifs,
>>>> + priv_nifs + 1, sizeof(*ifc))) == NULL) {
>>>> + log_warn("failed to grow interface table");
>>>> + goto fail;
>>>> + }
>>>> + priv_ifs = ifc;
>>>> + memcpy(&priv_ifs[priv_nifs], vifc, sizeof(*vifc));
>>>> + priv_nifs++;
>>>> +
>>>> + /* Sort table */
>>>> + qsort(priv_ifs, priv_nifs, sizeof(*ifc), priv_if_cmp);
>>>> +
>>>> + if (vifc->vifc_flags & VMD_IFC_UNIT) {
>>>> + if (vifc->vifc_unit == UINT_MAX) {
>>>> + log_warnx("interface tap%u unit too large",
>>>> + vifc->vifc_unit);
>>>> + goto fail;
>>>> + }
>>>> +
>>>> + if (vm_priv_byunit(vifc->vifc_unit) != NULL) {
>>>> + log_warnx("interface tap%u defined twice",
>>>> + vifc->vifc_unit);
>>>> + goto fail;
>>>> + }
>>>> +
>>>> + /* Append new interface unit */
>>>> + if ((ifu = recallocarray(priv_ifunits, priv_nifunits,
>>>> + priv_nifunits + 1, sizeof(*ifu))) == NULL) {
>>>> + log_warn("failed to grow interface unit table");
>>>> + goto fail;
>>>> + }
>>>> + priv_ifunits = ifu;
>>>> + priv_ifunits[priv_nifunits++] = vifc->vifc_unit;
>>>> +
>>>> + /* Sort table */
>>>> + qsort(priv_ifunits, priv_nifunits, sizeof(*ifu),
>>>> + priv_ifunit_cmp);
>>>> +
>>>> + log_debug("%s: %s registered interface tap%u", __func__,
>>>> + ps->ps_title[privsep_process],
>>>> + vifc->vifc_unit);
>>>> + }
>>>> +
>>>> + return (0);
>>>> +
>>>> + fail:
>>>> + if (ifc != NULL)
>>>> + vm_priv_unregister(ps, vifc->vifc_vmid, vifc->vifc_idx);
>>>> + return (-1);
>>>> +}
>>>> +
>>>> +/*
>>>> + * Called to unregister global interface configuration
>>>> + */
>>>> +void
>>>> +vm_priv_unregister(struct privsep *ps, uint32_t vmid, int idx)
>>>> +{
>>>> + struct vmd_ifconfig *vifc, *ifc;
>>>> + unsigned int *ifu;
>>>> +
>>>> + if ((vifc = vm_priv_byid(vmid, idx)) == NULL)
>>>> + return;
>>>> +
>>>> + if (vifc->vifc_flags & VMD_IFC_UNIT &&
>>>> + (ifu = vm_priv_byunit(vifc->vifc_unit)) != NULL) {
>>>> + /* Move entry to the end */
>>>> + *ifu = UINT_MAX;
>>>> + qsort(priv_ifunits, priv_nifunits, sizeof(*ifu),
>>>> + priv_ifunit_cmp);
>>>> +
>>>> + /* and remove last entry from the table */
>>>> + if ((ifu = recallocarray(priv_ifunits, priv_nifunits,
>>>> + priv_nifunits - 1, sizeof(*ifu))) == NULL &&
>>>> + priv_nifunits > 1) {
>>>> + log_warn("failed to shrink interface unit table");
>>>> + return;
>>>> + }
>>>> + priv_ifunits = ifu;
>>>> + priv_nifunits--;
>>>> +
>>>> + log_debug("%s: %s unregistered interface tap%u", __func__,
>>>> + ps->ps_title[privsep_process],
>>>> + vifc->vifc_unit);
>>>> + }
>>>> +
>>>> + /* Move entry to the end */
>>>> + vifc->vifc_vmid = UINT32_MAX;
>>>> + qsort(priv_ifs, priv_nifs, sizeof(*ifc), priv_if_cmp);
>>>> +
>>>> + /* and remove last entry from the table */
>>>> + if ((ifc = recallocarray(priv_ifs, priv_nifs,
>>>> + priv_nifs - 1, sizeof(*ifc))) == NULL &&
>>>> + priv_nifs > 1) {
>>>> + log_warn("failed to shrink interface table");
>>>> + return;
>>>> + }
>>>> + priv_ifs = ifc;
>>>> + priv_nifs--;
>>>> +
>>>> + log_debug("%s: %s unregistered interface vm %u #%u", __func__,
>>>> + ps->ps_title[privsep_process], vmid, idx);
>>>> +}
>>>> +
>>>> uint32_t
>>>> -vm_priv_addr(struct vmd_config *cfg, uint32_t vmid, int idx, int isvm)
>>>> +vm_priv_addr(struct vmd_config *cfg, uint32_t vmid, int idx, int isvm,
>>>> + struct in_addr *mask)
>>>> {
>>>> struct address *h = &cfg->cfg_localprefix;
>>>> - in_addr_t prefix, mask, addr;
>>>> + in_addr_t prefix, addr;
>>>> + struct vmd_ifconfig *vifc;
>>>> +
>>>> + /* Check if there is a preconfigured address for this interface */
>>>> + if ((vifc = vm_priv_byid(vmid, idx)) != NULL &&
>>>> + vifc->vifc_flags & VMD_IFC_ADDR4) {
>>>> + if (isvm)
>>>> + addr = vifc->vifc_addr4.sin_addr.s_addr;
>>>> + else
>>>> + addr = vifc->vifc_gw4.sin_addr.s_addr;
>>>> + mask->s_addr = prefixlen2mask(vifc->vifc_prefixlen4);
>>>> + return (addr);
>>>> + }
>>>>
>>>> /*
>>>> * 1. Set the address prefix and mask, 100.64.0.0/10 by default.
>>>> @@ -556,7 +765,7 @@ vm_priv_addr(struct vmd_config *cfg, uin
>>>> h->prefixlen < 0 || h->prefixlen > 32)
>>>> fatal("local prefix");
>>>> prefix = ss2sin(&h->ss)->sin_addr.s_addr;
>>>> - mask = prefixlen2mask(h->prefixlen);
>>>> + mask->s_addr = prefixlen2mask(h->prefixlen);
>>>>
>>>> /* 2. Encode the VM ID as a per-VM subnet range N, 100.64.N.0/24. */
>>>> addr = vmid << 8;
>>>> @@ -580,7 +789,7 @@ vm_priv_addr(struct vmd_config *cfg, uin
>>>> * - 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) {
>>>> + if (prefix != (addr & mask->s_addr) || idx >= 0x7f) {
>>>> log_warnx("%s: dhcp address range exceeded,"
>>>> " vm id %u interface %d", __func__, vmid, idx);
>>>> return (0);
>>>> @@ -591,21 +800,35 @@ vm_priv_addr(struct vmd_config *cfg, uin
>>>>
>>>> int
>>>> vm_priv_addr6(struct vmd_config *cfg, uint32_t vmid,
>>>> - int idx, int isvm, struct in6_addr *in6_addr)
>>>> + int idx, int isvm, struct in6_addr *in6_addr, struct in6_addr *mask)
>>>> {
>>>> struct address *h = &cfg->cfg_localprefix6;
>>>> - struct in6_addr addr, mask;
>>>> + struct in6_addr addr, *addrptr;
>>>> + struct vmd_ifconfig *vifc;
>>>> uint32_t addr4;
>>>> + struct in_addr mask4;
>>>> +
>>>> + /* Check if there is a preconfigured address for this interface */
>>>> + if ((vifc = vm_priv_byid(vmid, idx)) != NULL &&
>>>> + vifc->vifc_flags & VMD_IFC_ADDR6) {
>>>> + if (isvm)
>>>> + addrptr = &vifc->vifc_addr6.sin6_addr;
>>>> + else
>>>> + addrptr = &vifc->vifc_gw6.sin6_addr;
>>>> + memcpy(in6_addr, addrptr, sizeof(*in6_addr));
>>>> + prefixlen2mask6(vifc->vifc_prefixlen6, mask);
>>>> + return (0);
>>>> + }
>>>>
>>>> /* 1. Set the address prefix and mask, fd00::/8 by default. */
>>>> if (h->ss.ss_family != AF_INET6 ||
>>>> h->prefixlen < 0 || h->prefixlen > 128)
>>>> fatal("local prefix6");
>>>> addr = ss2sin6(&h->ss)->sin6_addr;
>>>> - prefixlen2mask6(h->prefixlen, &mask);
>>>> + prefixlen2mask6(h->prefixlen, mask);
>>>>
>>>> /* 2. Encode the VM IPv4 address as subnet, fd00::NN:NN:0:0/96. */
>>>> - if ((addr4 = vm_priv_addr(cfg, vmid, idx, 1)) == 0)
>>>> + if ((addr4 = vm_priv_addr(cfg, vmid, idx, 1, &mask4)) == 0)
>>>> return (0);
>>>> memcpy(&addr.s6_addr[8], &addr4, sizeof(addr4));
>>>>
>>>> Index: usr.sbin/vmd/vm.conf.5
>>>> ===================================================================
>>>> RCS file: /cvs/src/usr.sbin/vmd/vm.conf.5,v
>>>> retrieving revision 1.44
>>>> diff -u -p -u -p -r1.44 vm.conf.5
>>>> --- usr.sbin/vmd/vm.conf.5 14 May 2019 12:47:17 -0000 1.44
>>>> +++ usr.sbin/vmd/vm.conf.5 25 Oct 2019 18:11:05 -0000
>>>> @@ -209,6 +209,14 @@ to select a specific one.
>>>> .Pp
>>>> Valid options are:
>>>> .Bl -tag -width Ds
>>>> +.It Ic address Ar address Ns Li / Ns Ar prefix Ar gateway
>>>> +If the interface is configured as a
>>>> +.Cm local
>>>> +interface,
>>>> +use a static IP address and gateway.
>>>> +This option can be specified for IPv4 and for IPv6.
>>>> +If not specified, the default is to auto-generate the address pair using
>>>> the
>>>> +.Cm local Oo Cm inet6 Oc Cm prefix .
>>>> .It Cm group Ar group-name
>>>> Assign the interface to a specific interface
>>>> .Dq group .
>>>> @@ -258,6 +266,8 @@ A
>>>> interface will auto-generate an IPv4 subnet for the interface,
>>>> configure a gateway address on the VM host side,
>>>> and run a simple DHCP/BOOTP server for the VM.
>>>> +The address can optionally be configured as a static
>>>> +.Cm address .
>>>> This option can be used for layer 3 mode without configuring a switch.
>>>> .Pp
>>>> If the global
>>>> Index: usr.sbin/vmd/vmd.c
>>>> ===================================================================
>>>> RCS file: /cvs/src/usr.sbin/vmd/vmd.c,v
>>>> retrieving revision 1.116
>>>> diff -u -p -u -p -r1.116 vmd.c
>>>> --- usr.sbin/vmd/vmd.c 4 Sep 2019 07:02:03 -0000 1.116
>>>> +++ usr.sbin/vmd/vmd.c 25 Oct 2019 18:11:05 -0000
>>>> @@ -1161,6 +1161,8 @@ void
>>>> vm_remove(struct vmd_vm *vm, const char *caller)
>>>> {
>>>> struct privsep *ps = &env->vmd_ps;
>>>> + size_t i;
>>>> + int idx;
>>>>
>>>> if (vm == NULL)
>>>> return;
>>>> @@ -1171,6 +1173,16 @@ vm_remove(struct vmd_vm *vm, const char
>>>>
>>>> TAILQ_REMOVE(env->vmd_vms, vm, vm_entry);
>>>>
>>>> + for (i = 0; i < vm->vm_params.vmc_params.vcp_nnics; i++) {
>>>> + idx = (int)i;
>>>> + vm_priv_unregister(ps, vm->vm_vmid, idx);
>>>> + if (privsep_process == PROC_PARENT) {
>>>> + proc_compose_imsg(ps, PROC_PRIV, -1,
>>>> + IMSG_VMDOP_IF_UNREGISTER,
>>>> + vm->vm_vmid, -1, &idx, sizeof(idx));
>>>> + }
>>>> + }
>>>> +
>>>> user_put(vm->vm_user);
>>>> vm_stop(vm, 0, caller);
>>>> free(vm);
>>>> @@ -1211,14 +1223,17 @@ int
>>>> vm_register(struct privsep *ps, struct vmop_create_params *vmc,
>>>> struct vmd_vm **ret_vm, uint32_t id, uid_t uid)
>>>> {
>>>> - struct vmd_vm *vm = NULL, *vm_parent = NULL;
>>>> + char ifname[IF_NAMESIZE], *s;
>>>> + struct vmd_vm *vm = NULL, *vm_new = NULL, *vm_parent = NULL;
>>>> struct vm_create_params *vcp = &vmc->vmc_params;
>>>> struct vmop_owner *vmo = NULL;
>>>> + struct vmop_address *vma;
>>>> struct vmd_user *usr = NULL;
>>>> + struct vmd_ifconfig vifc;
>>>> + int maxprefixlen;
>>>> uint32_t nid, rng;
>>>> unsigned int i, j;
>>>> struct vmd_switch *sw;
>>>> - char *s;
>>>>
>>>> /* Check if this is an instance of another VM */
>>>> if (vm_instance(ps, &vm_parent, vmc, uid) == -1)
>>>> @@ -1294,7 +1309,7 @@ vm_register(struct privsep *ps, struct v
>>>> goto fail;
>>>> }
>>>>
>>>> - if ((vm = calloc(1, sizeof(*vm))) == NULL)
>>>> + if ((vm = vm_new = calloc(1, sizeof(*vm))) == NULL)
>>>> goto fail;
>>>>
>>>> memcpy(&vm->vm_params, vmc, sizeof(vm->vm_params));
>>>> @@ -1305,6 +1320,20 @@ vm_register(struct privsep *ps, struct v
>>>> vm->vm_receive_fd = -1;
>>>> vm->vm_state &= ~VM_STATE_PAUSED;
>>>> vm->vm_user = usr;
>>>> + vm->vm_kernel = -1;
>>>> + vm->vm_cdrom = -1;
>>>> + vm->vm_iev.ibuf.fd = -1;
>>>> +
>>>> + /*
>>>> + * Assign a new internal Id if not specified and we succeed in
>>>> + * claiming a new Id.
>>>> + */
>>>> + if (id != 0)
>>>> + vm->vm_vmid = id;
>>>> + else if (vm_claimid(vcp->vcp_name, uid, &nid) == -1)
>>>> + goto fail;
>>>> + else
>>>> + vm->vm_vmid = nid;
>>>>
>>>> for (i = 0; i < VMM_MAX_DISKS_PER_VM; i++)
>>>> for (j = 0; j < VM_MAX_BASE_PER_DISK; j++)
>>>> @@ -1333,30 +1362,69 @@ vm_register(struct privsep *ps, struct v
>>>> vcp->vcp_macs[i][4] = rng;
>>>> vcp->vcp_macs[i][5] = rng >> 8;
>>>> }
>>>> - }
>>>> - vm->vm_kernel = -1;
>>>> - vm->vm_cdrom = -1;
>>>> - vm->vm_iev.ibuf.fd = -1;
>>>>
>>>> - /*
>>>> - * Assign a new internal Id if not specified and we succeed in
>>>> - * claiming a new Id.
>>>> - */
>>>> - if (id != 0)
>>>> - vm->vm_vmid = id;
>>>> - else if (vm_claimid(vcp->vcp_name, uid, &nid) == -1)
>>>> - goto fail;
>>>> - else
>>>> - vm->vm_vmid = nid;
>>>> + /*
>>>> + * Store interface in global configuration table
>>>> + */
>>>> + memset(&vifc, 0, sizeof(vifc));
>>>> +
>>>> + /* Get and check pre-configured interface name */
>>>> + s = vmc->vmc_ifnames[i];
>>>> + if (*s != '\0' && strcmp("tap", s) != 0 &&
>>>> + priv_getiftype(s, ifname, &vifc.vifc_unit) != -1)
>>>> + vifc.vifc_flags |= VMD_IFC_UNIT;
>>>> +
>>>> + maxprefixlen = 0;
>>>> + if (vmc->vmc_ifflags[i] & VMIFF_ADDR4) {
>>>> + vma = &vmc->vmc_ifaddr4[i];
>>>> + memcpy(&vifc.vifc_addr4, &vma->vma_addr,
>>>> + sizeof(vifc.vifc_addr4));
>>>> + memcpy(&vifc.vifc_gw4, &vma->vma_gw,
>>>> + sizeof(vifc.vifc_gw4));
>>>> + vifc.vifc_prefixlen4 = vma->vma_prefixlen;
>>>> + vifc.vifc_flags |= VMD_IFC_ADDR4;
>>>> + maxprefixlen = 127;
>>>> + }
>>>> + if (vmc->vmc_ifflags[i] & VMIFF_ADDR6) {
>>>> + vma = &vmc->vmc_ifaddr6[i];
>>>> + memcpy(&vifc.vifc_addr4, &vma->vma_addr,
>>>> + sizeof(vifc.vifc_addr4));
>>>> + memcpy(&vifc.vifc_gw4, &vma->vma_gw,
>>>> + sizeof(vifc.vifc_gw4));
>>>> + vifc.vifc_prefixlen4 = vma->vma_prefixlen;
>>>> + vifc.vifc_flags |= VMD_IFC_ADDR6;
>>>> + maxprefixlen = 31;
>>>> + }
>>>> + if (maxprefixlen && vma->vma_prefixlen > maxprefixlen) {
>>>> + log_warnx("address prefix larger than /%d",
>>>> + maxprefixlen);
>>>> + goto fail;
>>>> + }
>>>> +
>>>> + vifc.vifc_vmid = vm->vm_vmid;
>>>> + vifc.vifc_idx = i;
>>>> +
>>>> + if (vm_priv_register(ps, &vifc) == -1)
>>>> + goto fail;
>>>> +
>>>> + if (privsep_process == PROC_PARENT) {
>>>> + proc_compose_imsg(ps, PROC_PRIV, -1,
>>>> + IMSG_VMDOP_IF_REGISTER, -1, -1,
>>>> + &vifc, sizeof(vifc));
>>>> + }
>>>> + }
>>>>
>>>> log_debug("%s: registering vm %d", __func__, vm->vm_vmid);
>>>> TAILQ_INSERT_TAIL(env->vmd_vms, vm, vm_entry);
>>>>
>>>> *ret_vm = vm;
>>>> return (0);
>>>> +
>>>> fail:
>>>> + free(vm_new);
>>>> if (errno == 0)
>>>> errno = EINVAL;
>>>> +
>>>> return (-1);
>>>> }
>>>>
>>>> @@ -1956,6 +2024,71 @@ get_string(uint8_t *ptr, size_t len)
>>>> break;
>>>>
>>>> return strndup(ptr, i);
>>>> +}
>>>> +
>>>> +uint8_t
>>>> +mask2prefixlen(struct sockaddr *sa)
>>>> +{
>>>> + struct sockaddr_in *sa_in = (struct sockaddr_in *)sa;
>>>> + in_addr_t ina = sa_in->sin_addr.s_addr;
>>>> +
>>>> + if (ina == 0)
>>>> + return (0);
>>>> + else
>>>> + return (33 - ffs(ntohl(ina)));
>>>> +}
>>>> +
>>>> +uint8_t
>>>> +mask2prefixlen6(struct sockaddr *sa)
>>>> +{
>>>> + struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *)sa;
>>>> + uint8_t *ap, *ep;
>>>> + unsigned int l = 0;
>>>> +
>>>> + /*
>>>> + * sin6_len is the size of the sockaddr so substract the offset of
>>>> + * the possibly truncated sin6_addr struct.
>>>> + */
>>>> + ap = (uint8_t *)&sa_in6->sin6_addr;
>>>> + ep = (uint8_t *)sa_in6 + sa_in6->sin6_len;
>>>> + for (; ap < ep; ap++) {
>>>> + /* this "beauty" is adopted from sbin/route/show.c ... */
>>>> + switch (*ap) {
>>>> + case 0xff:
>>>> + l += 8;
>>>> + break;
>>>> + case 0xfe:
>>>> + l += 7;
>>>> + goto done;
>>>> + case 0xfc:
>>>> + l += 6;
>>>> + goto done;
>>>> + case 0xf8:
>>>> + l += 5;
>>>> + goto done;
>>>> + case 0xf0:
>>>> + l += 4;
>>>> + goto done;
>>>> + case 0xe0:
>>>> + l += 3;
>>>> + goto done;
>>>> + case 0xc0:
>>>> + l += 2;
>>>> + goto done;
>>>> + case 0x80:
>>>> + l += 1;
>>>> + goto done;
>>>> + case 0x00:
>>>> + goto done;
>>>> + default:
>>>> + fatalx("non contiguous inet6 netmask");
>>>> + }
>>>> + }
>>>> +
>>>> +done:
>>>> + if (l > sizeof(struct in6_addr) * 8)
>>>> + fatalx("%s: prefixlen %d out of bound", __func__, l);
>>>> + return (l);
>>>> }
>>>>
>>>> uint32_t
>>>> Index: usr.sbin/vmd/vmd.h
>>>> ===================================================================
>>>> RCS file: /cvs/src/usr.sbin/vmd/vmd.h,v
>>>> retrieving revision 1.97
>>>> diff -u -p -u -p -r1.97 vmd.h
>>>> --- usr.sbin/vmd/vmd.h 7 Sep 2019 09:11:14 -0000 1.97
>>>> +++ usr.sbin/vmd/vmd.h 25 Oct 2019 18:11:06 -0000
>>>> @@ -119,6 +119,8 @@ enum imsg_type {
>>>> IMSG_VMDOP_PRIV_IFRDOMAIN,
>>>> IMSG_VMDOP_VM_SHUTDOWN,
>>>> IMSG_VMDOP_VM_REBOOT,
>>>> + IMSG_VMDOP_IF_REGISTER,
>>>> + IMSG_VMDOP_IF_UNREGISTER,
>>>> IMSG_VMDOP_CONFIG,
>>>> IMSG_VMDOP_DONE
>>>> };
>>>> @@ -160,6 +162,12 @@ struct vmop_owner {
>>>> int64_t gid;
>>>> };
>>>>
>>>> +struct vmop_address {
>>>> + struct sockaddr_storage vma_addr;
>>>> + struct sockaddr_storage vma_gw;
>>>> + int vma_prefixlen;
>>>> +};
>>>> +
>>>> struct vmop_create_params {
>>>> struct vm_create_params vmc_params;
>>>> unsigned int vmc_flags;
>>>> @@ -185,7 +193,10 @@ struct vmop_create_params {
>>>> #define VMIFF_LOCKED 0x02
>>>> #define VMIFF_LOCAL 0x04
>>>> #define VMIFF_RDOMAIN 0x08
>>>> -#define VMIFF_OPTMASK (VMIFF_LOCKED|VMIFF_LOCAL|VMIFF_RDOMAIN)
>>>> +#define VMIFF_ADDR4 0x10
>>>> +#define VMIFF_ADDR6 0x20
>>>> +#define VMIFF_OPTMASK \
>>>> + (VMIFF_LOCKED|VMIFF_LOCAL|VMIFF_RDOMAIN|VMIFF_ADDR4|VMIFF_ADDR6)
>>>>
>>>> unsigned int vmc_disktypes[VMM_MAX_DISKS_PER_VM];
>>>> unsigned int vmc_diskbases[VMM_MAX_DISKS_PER_VM];
>>>> @@ -196,6 +207,8 @@ struct vmop_create_params {
>>>> char vmc_ifswitch[VMM_MAX_NICS_PER_VM][VM_NAME_MAX];
>>>> char vmc_ifgroup[VMM_MAX_NICS_PER_VM][IF_NAMESIZE];
>>>> unsigned int vmc_ifrdomain[VMM_MAX_NICS_PER_VM];
>>>> + struct vmop_address vmc_ifaddr4[VMM_MAX_NICS_PER_VM];
>>>> + struct vmop_address vmc_ifaddr6[VMM_MAX_NICS_PER_VM];
>>>> struct vmop_owner vmc_owner;
>>>>
>>>> /* instance template params */
>>>> @@ -315,6 +328,26 @@ struct address {
>>>> };
>>>> TAILQ_HEAD(addresslist, address);
>>>>
>>>> +struct vmd_ifconfig {
>>>> + uint32_t vifc_vmid; /* associated VM id */
>>>> + unsigned int vifc_idx; /* relative interface index */
>>>> +
>>>> + unsigned int vifc_flags;
>>>> +#define VMD_IFC_UNIT 0x01 /* has interface tap(4) unit */
>>>> +#define VMD_IFC_ADDR4 0x02 /* has IPv4 address */
>>>> +#define VMD_IFC_ADDR6 0x04 /* has IPv6 address */
>>>> +
>>>> + unsigned int vifc_unit;
>>>> +
>>>> + struct sockaddr_in vifc_addr4;
>>>> + struct sockaddr_in vifc_gw4;
>>>> + int vifc_prefixlen4;
>>>> +
>>>> + struct sockaddr_in6 vifc_addr6;
>>>> + struct sockaddr_in6 vifc_gw6;
>>>> + int vifc_prefixlen6;
>>>> +};
>>>> +
>>>> struct vmd_config {
>>>> unsigned int cfg_flags;
>>>> #define VMD_CFG_INET6 0x01
>>>> @@ -391,6 +424,7 @@ void vm_stop(struct vmd_vm *, int, cons
>>>> void vm_remove(struct vmd_vm *, const char *);
>>>> int vm_register(struct privsep *, struct vmop_create_params *,
>>>> struct vmd_vm **, uint32_t, uid_t);
>>>> +void vm_priv_unregister(struct privsep *, uint32_t, int);
>>>> int vm_checkperm(struct vmd_vm *, struct vmop_owner *, uid_t);
>>>> int vm_checkaccess(int, unsigned int, uid_t, int);
>>>> int vm_opentty(struct vmd_vm *);
>>>> @@ -402,6 +436,8 @@ void user_put(struct vmd_user *);
>>>> void user_inc(struct vm_create_params *, struct vmd_user *, int);
>>>> int user_checklimit(struct vmd_user *, struct vm_create_params *);
>>>> char *get_string(uint8_t *, size_t);
>>>> +uint8_t mask2prefixlen(struct sockaddr *);
>>>> +uint8_t mask2prefixlen6(struct sockaddr *);
>>>> uint32_t prefixlen2mask(uint8_t);
>>>> void prefixlen2mask6(u_int8_t, struct in6_addr *);
>>>> void getmonotime(struct timeval *);
>>>> @@ -411,11 +447,15 @@ void priv(struct privsep *, struct priv
>>>> int priv_getiftype(char *, char *, unsigned int *);
>>>> int priv_findname(const char *, const char **);
>>>> int priv_validgroup(const char *);
>>>> +int vm_priv_register(struct privsep *, struct vmd_ifconfig *);
>>>> int vm_priv_ifconfig(struct privsep *, struct vmd_vm *);
>>>> int vm_priv_brconfig(struct privsep *, struct vmd_switch *);
>>>> -uint32_t vm_priv_addr(struct vmd_config *, uint32_t, int, int);
>>>> +uint32_t vm_priv_addr(struct vmd_config *, uint32_t, int, int,
>>>> + struct in_addr *);
>>>> int vm_priv_addr6(struct vmd_config *, uint32_t, int, int,
>>>> - struct in6_addr *);
>>>> + struct in6_addr *, struct in6_addr *);
>>>> +unsigned int *vm_priv_byunit(unsigned int);
>>>> +struct vmd_ifconfig *vm_priv_byid(uint32_t, int);
>>>>
>>>> /* vmm.c */
>>>> struct iovec;
>>>> Index: usr.sbin/vmd/vmm.c
>>>> ===================================================================
>>>> RCS file: /cvs/src/usr.sbin/vmd/vmm.c,v
>>>> retrieving revision 1.94
>>>> diff -u -p -u -p -r1.94 vmm.c
>>>> --- usr.sbin/vmd/vmm.c 25 Oct 2019 09:57:33 -0000 1.94
>>>> +++ usr.sbin/vmd/vmm.c 25 Oct 2019 18:11:06 -0000
>>>> @@ -602,6 +602,9 @@ opentap(char *ifname)
>>>> char path[PATH_MAX];
>>>>
>>>> for (i = 0; i < MAX_TAP; i++) {
>>>> + /* Skip statically configured interface names (eg. tap0) */
>>>> + if (vm_priv_byunit(i) != NULL)
>>>> + continue;
>>>> snprintf(path, PATH_MAX, "/dev/tap%d", i);
>>>> fd = open(path, O_RDWR | O_NONBLOCK);
>>>> if (fd != -1) {
>>>>
>>
>