tech@,

While migrating an existing -current vm to use dhcpleased(8), one of the
issues discovered was the dhcp/bootp handling built into vmd(8) for
local interfaces was improperly missing packets sent to the ethernet
address of the host-side tap(4) device. (vmd(8) was only looking for
broadcast packets.)

The following diff:
- includes the host-side tap(4) lladdr in the packet filtering logic for
  intercepting vio(4) dhcp/bootp communication
- removes a conditional check (dhcpsz == 0) pointed out by deraadt@ that
  could contribute to missed packets while iterating through the vionet
  tx ring

Because of vmd(8)'s priv-sep design, the approach taken is to pass a
duplicate of the opened tap(4) fd to the "priv" process that is
unpledged and responsible for setting up networking. A response imsg
travels between processes, being forwarded until it gets to the intended
vm process.

For those looking to test the diff, some steps to follow are:

0. Don't apply the patch yet.
1. Use an OpenBSD guest running a recent snapshot that has the latest
dhcpleased(8)[1] and dhcpleasectl(8)[2] changes committed by florian@.
2. Configure the guest for using dhcpleased(8), ideally via
/etc/hostname.vio0 containing:

  inet autoconf
  up

3. Ensure you get an IP assigned from vmd(8) after boot.
4. Check the lease with `dhcpleasectl show interface vio0`. It should
   report [Bound] and have a _very_ long lease time.
5. Force a renewal with `dhcpleasectl send request vio0`.
6. Check again like in step 4. It will be "stuck" in a [Renewing] state.
7. Shut down the guest, vmd(8), and apply the patch.
8. Repeat steps 4-6, but the guest should no longer be "stuck" in
   [Renewing] and should report [Bound] after forced renewal.

You can also turn up debug logging for vmd(8) and should see
corresponding messages of:

  vionet: dhcp request, local response size 342

If you do not, the packet was not intercepted by vmd(8).

I have tested this diff with a variety of conditions, including multiple
emulated nics per guest. Specifically 1 nic using dhcp/bootp resolved by
vmd(8) and the other nic connected to a virtual switch using veb(4) and
an instance of dhcpd(8) running on the host. Also ran a snapshot upgrade
with no issues.

So in summary, if this diff works, you shouldn't notice a difference ;-)

Thanks,
-Dave

[1] https://marc.info/?l=openbsd-cvs&m=161643049815177&w=2
[2] https://marc.info/?l=openbsd-cvs&m=161652157122736&w=2


Index: config.c
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/config.c,v
retrieving revision 1.60
diff -u -p -r1.60 config.c
--- config.c    19 Mar 2021 09:29:33 -0000      1.60
+++ config.c    24 Mar 2021 15:42:13 -0000
@@ -227,6 +227,7 @@ config_setvm(struct privsep *ps, struct
        char                     base[PATH_MAX];
        unsigned int             unit;
        struct timeval           tv, rate, since_last;
+       struct vmop_addr_req     var;

        errno = 0;

@@ -499,6 +500,12 @@ config_setvm(struct privsep *ps, struct
                proc_compose_imsg(ps, PROC_VMM, -1,
                    IMSG_VMDOP_START_VM_IF, vm->vm_vmid, tapfds[i],
                    &i, sizeof(i));
+
+               memset(&var, 0, sizeof(var));
+               var.var_vmid = vm->vm_vmid;
+               var.var_nic_idx = i;
+               proc_compose_imsg(ps, PROC_PRIV, -1, IMSG_VMDOP_PRIV_GET_ADDR,
+                   vm->vm_vmid, dup(tapfds[i]), &var, sizeof(var));
        }

        if (!(vm->vm_state & VM_STATE_RECEIVED))
Index: dhcp.c
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/dhcp.c,v
retrieving revision 1.8
diff -u -p -r1.8 dhcp.c
--- dhcp.c      27 Dec 2018 19:51:30 -0000      1.8
+++ dhcp.c      24 Mar 2021 15:42:13 -0000
@@ -57,8 +57,11 @@ dhcp_request(struct vionet_dev *dev, cha
        if ((offset = decode_hw_header(buf, buflen, 0, &pc, HTYPE_ETHER)) < 0)
                return (-1);

-       if (memcmp(pc.pc_smac, dev->mac, ETHER_ADDR_LEN) != 0 ||
-           memcmp(pc.pc_dmac, broadcast, ETHER_ADDR_LEN) != 0)
+       if (memcmp(pc.pc_dmac, broadcast, ETHER_ADDR_LEN) != 0 &&
+           memcmp(pc.pc_dmac, dev->hostmac, ETHER_ADDR_LEN) != 0)
+               return (-1);
+
+       if (memcmp(pc.pc_smac, dev->mac, ETHER_ADDR_LEN) != 0)
                return (-1);

        if ((offset = decode_udp_ip_header(buf, buflen, offset, &pc)) < 0)
Index: priv.c
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/priv.c,v
retrieving revision 1.16
diff -u -p -r1.16 priv.c
--- priv.c      28 Feb 2021 22:56:09 -0000      1.16
+++ priv.c      24 Mar 2021 15:42:14 -0000
@@ -92,6 +92,8 @@ priv_dispatch_parent(int fd, struct priv
        struct ifaliasreq        ifra;
        struct in6_aliasreq      in6_ifra;
        struct if_afreq          ifar;
+       struct vmop_addr_req     vareq;
+       struct vmop_addr_result  varesult;
        char                     type[IF_NAMESIZE];

        switch (imsg->hdr.type) {
@@ -115,6 +117,7 @@ priv_dispatch_parent(int fd, struct priv
                break;
        case IMSG_VMDOP_CONFIG:
        case IMSG_CTL_RESET:
+       case IMSG_VMDOP_PRIV_GET_ADDR:
                break;
        default:
                return (-1);
@@ -244,6 +247,22 @@ 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_PRIV_GET_ADDR:
+               IMSG_SIZE_CHECK(imsg, &vareq);
+               memcpy(&vareq, imsg->data, sizeof(vareq));
+
+               varesult.var_vmid = vareq.var_vmid;
+               varesult.var_nic_idx = vareq.var_nic_idx;
+
+               /* resolve lladdr for the tap(4) and send back to parent */
+               if (ioctl(imsg->fd, SIOCGIFADDR, &varesult.var_addr) != 0)
+                       log_warn("SIOCGIFADDR");
+               else
+                       proc_compose_imsg(ps, PROC_PARENT, -1,
+                           IMSG_VMDOP_PRIV_GET_ADDR_RESPONSE, imsg->hdr.peerid,
+                           -1, &varesult, sizeof(varesult));
+               close(imsg->fd);
                break;
        case IMSG_VMDOP_CONFIG:
                config_getconfig(env, imsg);
Index: virtio.c
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/virtio.c,v
retrieving revision 1.82
diff -u -p -r1.82 virtio.c
--- virtio.c    11 Dec 2019 06:45:16 -0000      1.82
+++ virtio.c    24 Mar 2021 15:42:14 -0000
@@ -1510,7 +1510,7 @@ vionet_notify_tx(struct vionet_dev *dev)
                        log_debug("vionet: wrong source address %s for vm %d",
                            ether_ntoa((struct ether_addr *)
                            eh->ether_shost), dev->vm_id);
-               else if (dev->local && dhcpsz == 0 &&
+               else if (dev->local &&
                    (dhcpsz = dhcp_request(dev, pkt, pktsz, &dhcppkt)) != -1) {
                        log_debug("vionet: dhcp request,"
                            " local response size %zd", dhcpsz);
@@ -2033,6 +2033,32 @@ virtio_init(struct vmd_vm *vm, int child
        vmmci.pci_id = id;

        evtimer_set(&vmmci.timeout, vmmci_timeout, NULL);
+}
+
+/*
+ * vionet_set_hostmac
+ *
+ * Sets the hardware address for the host-side tap(4) on a vionet_dev.
+ *
+ * This should only be called from the event-loop thread
+ *
+ * vm: pointer to the current vmd_vm instance
+ * idx: index into the array of vionet_dev's for the target vionet_dev
+ * addr: ethernet address to set
+ */
+void
+vionet_set_hostmac(struct vmd_vm *vm, unsigned int idx,
+    uint8_t addr[ETHER_ADDR_LEN])
+{
+       struct vmop_create_params *vmc = &vm->vm_params;
+       struct vm_create_params   *vcp = &vmc->vmc_params;
+       struct vionet_dev         *dev;
+
+       if (idx > vcp->vcp_nnics)
+               fatalx("vionet_set_hostmac");
+
+       dev = &vionet[idx];
+       memcpy(dev->hostmac, addr, sizeof(dev->hostmac));
 }

 void
Index: virtio.h
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/virtio.h,v
retrieving revision 1.36
diff -u -p -r1.36 virtio.h
--- virtio.h    7 Jan 2021 17:11:38 -0000       1.36
+++ virtio.h    24 Mar 2021 15:42:14 -0000
@@ -208,6 +208,7 @@ struct vionet_dev {
        uint32_t vm_vmid;
        int irq;
        uint8_t mac[6];
+       uint8_t hostmac[6];

        int idx;
        int lockedmac;
@@ -298,6 +299,7 @@ void vionet_notify_rx(struct vionet_dev
 int vionet_notify_tx(struct vionet_dev *);
 void vionet_process_rx(uint32_t);
 int vionet_enq_rx(struct vionet_dev *, char *, ssize_t, int *);
+void vionet_set_hostmac(struct vmd_vm *, unsigned int, 
uint8_t[ETHER_ADDR_LEN]);

 int vmmci_io(int, uint16_t, uint32_t *, uint8_t *, void *, uint8_t);
 int vmmci_dump(int);
Index: vm.c
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/vm.c,v
retrieving revision 1.60
diff -u -p -r1.60 vm.c
--- vm.c        19 Mar 2021 09:29:33 -0000      1.60
+++ vm.c        24 Mar 2021 15:42:14 -0000
@@ -394,6 +394,7 @@ vm_dispatch_vmm(int fd, short event, voi
 {
        struct vmd_vm           *vm = arg;
        struct vmop_result       vmr;
+       struct vmop_addr_result  var;
        struct imsgev           *iev = &vm->vm_iev;
        struct imsgbuf          *ibuf = &iev->ibuf;
        struct imsg              imsg;
@@ -470,6 +471,16 @@ vm_dispatch_vmm(int fd, short event, voi
                                imsg_flush(&current_vm->vm_iev.ibuf);
                                _exit(0);
                        }
+                       break;
+               case IMSG_VMDOP_PRIV_GET_ADDR_RESPONSE:
+                       IMSG_SIZE_CHECK(&imsg, &var);
+                       memcpy(&var, imsg.data, sizeof(var));
+
+                       log_debug("%s: received tap addr %s for nic %d",
+                           vm->vm_params.vmc_params.vcp_name,
+                           ether_ntoa((void *)var.var_addr), var.var_nic_idx);
+
+                       vionet_set_hostmac(vm, var.var_nic_idx, var.var_addr);
                        break;
                default:
                        fatalx("%s: got invalid imsg %d from %s",
Index: vmd.c
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/vmd.c,v
retrieving revision 1.120
diff -u -p -r1.120 vmd.c
--- vmd.c       27 Jan 2021 07:21:54 -0000      1.120
+++ vmd.c       24 Mar 2021 15:42:14 -0000
@@ -58,6 +58,7 @@ void   vmd_shutdown(void);
 int     vmd_control_run(void);
 int     vmd_dispatch_control(int, struct privsep_proc *, struct imsg *);
 int     vmd_dispatch_vmm(int, struct privsep_proc *, struct imsg *);
+int     vmd_dispatch_priv(int, struct privsep_proc *, struct imsg *);
 int     vmd_check_vmh(struct vm_dump_header *);

 int     vm_instance(struct privsep *, struct vmd_vm **,
@@ -70,7 +71,7 @@ struct vmd    *env;

 static struct privsep_proc procs[] = {
        /* Keep "priv" on top as procs[0] */
-       { "priv",       PROC_PRIV,      NULL, priv },
+       { "priv",       PROC_PRIV,      vmd_dispatch_priv, priv },
        { "control",    PROC_CONTROL,   vmd_dispatch_control, control },
        { "vmm",        PROC_VMM,       vmd_dispatch_vmm, vmm, vmm_shutdown },
 };
@@ -542,6 +543,24 @@ vmd_dispatch_vmm(int fd, struct privsep_
                }
                IMSG_SIZE_CHECK(imsg, &res);
                proc_forward_imsg(ps, imsg, PROC_CONTROL, -1);
+               break;
+       default:
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+vmd_dispatch_priv(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+       struct vmop_addr_result  var;
+
+       switch (imsg->hdr.type) {
+       case IMSG_VMDOP_PRIV_GET_ADDR_RESPONSE:
+               IMSG_SIZE_CHECK(imsg, &var);
+               memcpy(&var, imsg->data, sizeof(var));
+               proc_forward_imsg(p->p_ps, imsg, PROC_VMM, -1);
                break;
        default:
                return (-1);
Index: vmd.h
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/vmd.h,v
retrieving revision 1.102
diff -u -p -r1.102 vmd.h
--- vmd.h       19 Mar 2021 09:29:33 -0000      1.102
+++ vmd.h       24 Mar 2021 15:42:14 -0000
@@ -120,6 +120,8 @@ enum imsg_type {
        IMSG_VMDOP_PRIV_IFADDR,
        IMSG_VMDOP_PRIV_IFADDR6,
        IMSG_VMDOP_PRIV_IFRDOMAIN,
+       IMSG_VMDOP_PRIV_GET_ADDR,
+       IMSG_VMDOP_PRIV_GET_ADDR_RESPONSE,
        IMSG_VMDOP_VM_SHUTDOWN,
        IMSG_VMDOP_VM_REBOOT,
        IMSG_VMDOP_CONFIG,
@@ -156,6 +158,17 @@ struct vmop_ifreq {
        char                             vfr_value[VM_NAME_MAX];
        struct sockaddr_storage          vfr_addr;
        struct sockaddr_storage          vfr_mask;
+};
+
+struct vmop_addr_req {
+       uint32_t                 var_vmid;
+       unsigned int             var_nic_idx;
+};
+
+struct vmop_addr_result {
+       uint32_t                 var_vmid;
+       unsigned int             var_nic_idx;
+       uint8_t                  var_addr[ETHER_ADDR_LEN];
 };

 struct vmop_owner {
Index: vmm.c
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/vmm.c,v
retrieving revision 1.97
diff -u -p -r1.97 vmm.c
--- vmm.c       2 Mar 2021 02:56:22 -0000       1.97
+++ vmm.c       24 Mar 2021 15:42:14 -0000
@@ -109,6 +109,7 @@ vmm_dispatch_parent(int fd, struct privs
        struct vmop_id           vid;
        struct vmop_result       vmr;
        struct vmop_create_params vmc;
+       struct vmop_addr_result  var;
        uint32_t                 id = 0, peerid = imsg->hdr.peerid;
        pid_t                    pid = 0;
        unsigned int             mode, flags;
@@ -331,6 +332,18 @@ vmm_dispatch_parent(int fd, struct privs
                if ((id = vm_id2vmid(id, NULL)) == 0)
                        res = ENOENT;
                cmd = IMSG_VMDOP_START_VM_RESPONSE;
+               break;
+       case IMSG_VMDOP_PRIV_GET_ADDR_RESPONSE:
+               IMSG_SIZE_CHECK(imsg, &var);
+               memcpy(&var, imsg->data, sizeof(var));
+               if ((vm = vm_getbyvmid(var.var_vmid)) == NULL) {
+                       res = ENOENT;
+                       break;
+               }
+               /* Forward hardware address details to the guest vm */
+               imsg_compose_event(&vm->vm_iev,
+                   imsg->hdr.type, imsg->hdr.peerid, imsg->hdr.pid,
+                   imsg->fd, &var, sizeof(var));
                break;
        default:
                return (-1);

Reply via email to