The DHCP client available in the Alpine Linux installer (udhcpc, part
of BusyBox) does not accept responses that do not include the DHCP
message type option. Worse, it expects the message type to be
DHCPOFFER in some circumstances and DHCPREQUEST in others. The DHCP
server in vmd omits this option entirely, which makes it impossible
to install Alpine Linux in a virtual machine configured with "-L".

The simplest fix would be to use "resp.options[6] == DHCPOFFER" instead
of is_discover (see the patch below) because in practice the DHCP
message type will be the first option present after the magic cookie.
This was the first thing I tried, and it worked. But it's incorrect.

RFC 1534 says that requests with no message type can be treated as
BOOTP and not DHCP messages. It also says that we can send DHCP options
to BOOTP messages if we so desire, so it doesn't really matter whether
we initialize is_discover to zero or one.

Note that udhcpc also complains about two more options (server ID and
lease time) that are missing from the response message. I didn't do
anything about this because udhcpc uses sensible defaults.

I've tested this change with udhcpc (in a virtual Alpine Linux system)
and dhclient (in a virtual OpenBSD system) and it works for both. I
have not tried anything else.

Regards,
Anthony Coulter

Index: 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
--- dhcp.c      24 Apr 2017 07:14:27 -0000      1.3
+++ dhcp.c      8 Sep 2017 04:12:10 -0000
@@ -44,6 +44,7 @@ dhcp_request(struct vionet_dev *dev, cha
        struct dhcp_packet       req, resp;
        struct in_addr           in, mask;
        size_t                   resplen, o;
+       int                      is_discover = 1;
 
        if (buflen < (ssize_t)(BOOTP_MIN_LEN + sizeof(struct ether_header)))
                return (-1);
@@ -76,6 +77,15 @@ dhcp_request(struct vionet_dev *dev, cha
        if (req.ciaddr.s_addr != 0 || req.file[0] != '\0' || req.hops != 0)
                return (-1);
 
+       for (o = DHCP_OPTIONS_COOKIE_LEN;
+           o + offsetof(struct dhcp_packet, options) < buflen &&
+           req.options[o] != DHO_END;
+           o += req.options[o+1] + 2)
+               if (req.options[o] == DHO_DHCP_MESSAGE_TYPE) {
+                       is_discover = (req.options[o+2] == DHCPDISCOVER);
+                       break;
+               }
+
        memset(&resp, 0, sizeof(resp));
        resp.op = BOOTREPLY;
        resp.htype = req.htype;
@@ -123,6 +133,10 @@ dhcp_request(struct vionet_dev *dev, cha
        memcpy(&resp.options,
            DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN);
        o+= DHCP_OPTIONS_COOKIE_LEN;
+
+       resp.options[o++] = DHO_DHCP_MESSAGE_TYPE;
+       resp.options[o++] = 1;
+       resp.options[o++] = is_discover ? DHCPOFFER : DHCPACK;
 
        resp.options[o++] = DHO_SUBNET_MASK;
        resp.options[o++] = sizeof(mask);

Reply via email to