Hi,

the problem got reported a few times and a similar diff was floating
around: vmd's "local interface" implements a simple auto-magic BOOTP
server, but udhcpc doesn't support BOOTP, which is the predecessor and
a valid subset of DHCP.  udhcpc is used by busybox and many embedded
Linux distributions, maybe even by Android.

The following diff adds minimal DHCP support which works with udhcpc.

OK?

Reyk

Index: usr.sbin/vmd/dhcp.c
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/dhcp.c,v
retrieving revision 1.3
diff -u -p -u -p -r1.3 dhcp.c
--- usr.sbin/vmd/dhcp.c 24 Apr 2017 07:14:27 -0000      1.3
+++ usr.sbin/vmd/dhcp.c 2 Nov 2017 21:15:03 -0000
@@ -22,6 +22,7 @@
 #include <net/if.h>
 #include <netinet/in.h>
 #include <netinet/if_ether.h>
+#include <arpa/inet.h>
 
 #include <stdlib.h>
 #include <string.h>
@@ -38,12 +39,13 @@ extern struct vmd *env;
 ssize_t
 dhcp_request(struct vionet_dev *dev, char *buf, size_t buflen, char **obuf)
 {
-       unsigned char           *respbuf = NULL;
+       unsigned char           *respbuf = NULL, *op, *oe, dhcptype = 0;
        ssize_t                  offset, respbuflen = 0;
        struct packet_ctx        pc;
        struct dhcp_packet       req, resp;
-       struct in_addr           in, mask;
+       struct in_addr           server_addr, mask, client_addr, requested_addr;
        size_t                   resplen, o;
+       uint32_t                 ltime;
 
        if (buflen < (ssize_t)(BOOTP_MIN_LEN + sizeof(struct ether_header)))
                return (-1);
@@ -76,24 +78,54 @@ dhcp_request(struct vionet_dev *dev, cha
        if (req.ciaddr.s_addr != 0 || req.file[0] != '\0' || req.hops != 0)
                return (-1);
 
+       /* Get a few DHCP options (best effort as we fall back to BOOTP) */
+       if (memcmp(&req.options,
+           DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN) == 0) {
+               memset(&requested_addr, 0, sizeof(requested_addr));
+               op = req.options + DHCP_OPTIONS_COOKIE_LEN;
+               oe = req.options + sizeof(req.options);
+               while (*op != DHO_END && op < oe) {
+                       if (op[0] == DHO_PAD) {
+                               op++;
+                               continue;
+                       }
+                       if (op + 1 + op[1] >= oe)
+                               break;
+                       if (op[0] == DHO_DHCP_MESSAGE_TYPE &&
+                           op[1] == 1)
+                               dhcptype = op[2];
+                       else if (op[0] == DHO_DHCP_REQUESTED_ADDRESS &&
+                           op[1] == sizeof(requested_addr))
+                               memcpy(&requested_addr, &op[2],
+                                   sizeof(requested_addr));
+                       op += 2 + op[1];
+               }
+       }
+
        memset(&resp, 0, sizeof(resp));
        resp.op = BOOTREPLY;
        resp.htype = req.htype;
        resp.hlen = req.hlen;
        resp.xid = req.xid;
 
-       if ((in.s_addr = vm_priv_addr(&env->vmd_cfg.cfg_localprefix,
+       if ((client_addr.s_addr =
+           vm_priv_addr(&env->vmd_cfg.cfg_localprefix,
            dev->vm_vmid, dev->idx, 1)) == 0)
                return (-1);
-       memcpy(&resp.yiaddr, &in, sizeof(in));
-       memcpy(&ss2sin(&pc.pc_dst)->sin_addr, &in, sizeof(in));
+       memcpy(&resp.yiaddr, &client_addr,
+           sizeof(client_addr));
+       memcpy(&ss2sin(&pc.pc_dst)->sin_addr, &client_addr,
+           sizeof(client_addr));
        ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT);
 
-       if ((in.s_addr = vm_priv_addr(&env->vmd_cfg.cfg_localprefix,
+       if ((server_addr.s_addr =
+           vm_priv_addr(&env->vmd_cfg.cfg_localprefix,
            dev->vm_vmid, dev->idx, 0)) == 0)
                return (-1);
-       memcpy(&resp.siaddr, &in, sizeof(in));
-       memcpy(&ss2sin(&pc.pc_src)->sin_addr, &in, sizeof(in));
+       memcpy(&resp.siaddr, &server_addr,
+           sizeof(server_addr));
+       memcpy(&ss2sin(&pc.pc_src)->sin_addr, &server_addr,
+           sizeof(server_addr));
        ss2sin(&pc.pc_src)->sin_port = htons(SERVER_PORT);
 
        /* Packet is already allocated */
@@ -124,6 +156,44 @@ dhcp_request(struct vionet_dev *dev, cha
            DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN);
        o+= DHCP_OPTIONS_COOKIE_LEN;
 
+       /* Did we receive a DHCP request or was it just BOOTP? */
+       if (dhcptype) {
+               /*
+                * There is no need for a real state machine as we always
+                * answer with the same client IP and options for the VM.
+                */
+               if (dhcptype == DHCPDISCOVER)
+                       dhcptype = DHCPOFFER;
+               else if (dhcptype == DHCPREQUEST &&
+                   (requested_addr.s_addr == 0 ||
+                   client_addr.s_addr == requested_addr.s_addr))
+                       dhcptype = DHCPACK;
+               else
+                       dhcptype = DHCPNAK;
+
+               resp.options[o++] = DHO_DHCP_MESSAGE_TYPE;
+               resp.options[o++] = sizeof(dhcptype);
+               memcpy(&resp.options[o], &dhcptype, sizeof(dhcptype));
+               o += sizeof(dhcptype);
+
+               /* Our lease never changes, use the maximum lease time */
+               resp.options[o++] = DHO_DHCP_LEASE_TIME;
+               resp.options[o++] = sizeof(ltime);
+               ltime = ntohl(0xffffffff);
+               memcpy(&resp.options[o], &ltime, sizeof(ltime));
+               o += sizeof(ltime);
+
+               resp.options[o++] = DHO_DHCP_SERVER_IDENTIFIER;
+               resp.options[o++] = sizeof(server_addr);
+               memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
+               o += sizeof(server_addr);
+       }
+
+       resp.options[o++] = DHO_DOMAIN_NAME_SERVERS;
+       resp.options[o++] = sizeof(server_addr);
+       memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
+       o += sizeof(server_addr);
+
        resp.options[o++] = DHO_SUBNET_MASK;
        resp.options[o++] = sizeof(mask);
        mask.s_addr = htonl(0xfffffffe);
@@ -131,14 +201,14 @@ dhcp_request(struct vionet_dev *dev, cha
        o += sizeof(mask);
 
        resp.options[o++] = DHO_ROUTERS;
-       resp.options[o++] = sizeof(in);
-       memcpy(&resp.options[o], &in, sizeof(in));
-       o += sizeof(in);
+       resp.options[o++] = sizeof(server_addr);
+       memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
+       o += sizeof(server_addr);
 
        resp.options[o++] = DHO_DOMAIN_NAME_SERVERS;
-       resp.options[o++] = sizeof(in);
-       memcpy(&resp.options[o], &in, sizeof(in));
-       o += sizeof(in);
+       resp.options[o++] = sizeof(server_addr);
+       memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
+       o += sizeof(server_addr);
 
        resp.options[o++] = DHO_END;
 
@@ -163,4 +233,3 @@ dhcp_request(struct vionet_dev *dev, cha
        free(respbuf);
        return (0);
 }
-
Index: usr.sbin/vmd/vm.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/vmd/vm.conf.5,v
retrieving revision 1.23
diff -u -p -u -p -r1.23 vm.conf.5
--- usr.sbin/vmd/vm.conf.5      30 Oct 2017 03:37:33 -0000      1.23
+++ usr.sbin/vmd/vm.conf.5      2 Nov 2017 21:15:03 -0000
@@ -185,7 +185,7 @@ A
 .Cm local
 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.
+and run a simple DHCP/BOOTP server for the VM.
 This option can be used for layer 3 mode without configuring a switch.
 .It Cm interfaces Ar count
 Optional minimum number of network interfaces to add to the VM.
Index: usr.sbin/vmctl/vmctl.8
===================================================================
RCS file: /cvs/src/usr.sbin/vmctl/vmctl.8,v
retrieving revision 1.34
diff -u -p -u -p -r1.34 vmctl.8
--- usr.sbin/vmctl/vmctl.8      5 Sep 2017 22:06:49 -0000       1.34
+++ usr.sbin/vmctl/vmctl.8      2 Nov 2017 21:15:03 -0000
@@ -106,7 +106,7 @@ Add a local network interface.
 .Xr vmd 8
 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.
+and run a simple DHCP/BOOTP server for the VM.
 See
 .Sx LOCAL INTERFACES
 below for more information on how addresses are calculated and assigned when

Reply via email to