On Thu, Nov 02, 2017 at 10:26:55PM +0100, Reyk Floeter wrote:
> 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
>
no objections here, thanks.
-ml
> 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], <ime, 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
>