xref: /openbsd-src/usr.sbin/vmd/dhcp.c (revision f1dd7b858388b4a23f4f67a4957ec5ff656ebbe8)
1 /*	$OpenBSD: dhcp.c,v 1.9 2021/03/29 23:37:01 dv Exp $	*/
2 
3 /*
4  * Copyright (c) 2017 Reyk Floeter <reyk@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 
22 #include <net/if.h>
23 #include <netinet/in.h>
24 #include <netinet/if_ether.h>
25 #include <arpa/inet.h>
26 
27 #include <resolv.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <stddef.h>
31 
32 #include "proc.h"
33 #include "vmd.h"
34 #include "dhcp.h"
35 #include "virtio.h"
36 
37 static const uint8_t broadcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
38 extern struct vmd *env;
39 
40 ssize_t
41 dhcp_request(struct vionet_dev *dev, char *buf, size_t buflen, char **obuf)
42 {
43 	unsigned char		*respbuf = NULL, *op, *oe, dhcptype = 0;
44 	ssize_t			 offset, respbuflen = 0;
45 	struct packet_ctx	 pc;
46 	struct dhcp_packet	 req, resp;
47 	struct in_addr		 server_addr, mask, client_addr, requested_addr;
48 	size_t			 len, resplen, o;
49 	uint32_t		 ltime;
50 	struct vmd_vm		*vm;
51 	const char		*hostname = NULL;
52 
53 	if (buflen < (ssize_t)(BOOTP_MIN_LEN + sizeof(struct ether_header)))
54 		return (-1);
55 
56 	memset(&pc, 0, sizeof(pc));
57 	if ((offset = decode_hw_header(buf, buflen, 0, &pc, HTYPE_ETHER)) < 0)
58 		return (-1);
59 
60 	if (memcmp(pc.pc_dmac, broadcast, ETHER_ADDR_LEN) != 0 &&
61 	    memcmp(pc.pc_dmac, dev->hostmac, ETHER_ADDR_LEN) != 0)
62 		return (-1);
63 
64 	if (memcmp(pc.pc_smac, dev->mac, ETHER_ADDR_LEN) != 0)
65 		return (-1);
66 
67 	if ((offset = decode_udp_ip_header(buf, buflen, offset, &pc)) < 0)
68 		return (-1);
69 
70 	if (ntohs(ss2sin(&pc.pc_src)->sin_port) != CLIENT_PORT ||
71 	    ntohs(ss2sin(&pc.pc_dst)->sin_port) != SERVER_PORT)
72 		return (-1);
73 
74 	memset(&req, 0, sizeof(req));
75 	memcpy(&req, buf + offset, buflen - offset);
76 
77 	if (req.op != BOOTREQUEST ||
78 	    req.htype != pc.pc_htype ||
79 	    req.hlen != ETHER_ADDR_LEN ||
80 	    memcmp(dev->mac, req.chaddr, req.hlen) != 0)
81 		return (-1);
82 
83 	/* Ignore unsupported requests for now */
84 	if (req.ciaddr.s_addr != 0 || req.file[0] != '\0' || req.hops != 0)
85 		return (-1);
86 
87 	/* Get a few DHCP options (best effort as we fall back to BOOTP) */
88 	if (memcmp(&req.options,
89 	    DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN) == 0) {
90 		memset(&requested_addr, 0, sizeof(requested_addr));
91 		op = req.options + DHCP_OPTIONS_COOKIE_LEN;
92 		oe = req.options + sizeof(req.options);
93 		while (*op != DHO_END && op < oe) {
94 			if (op[0] == DHO_PAD) {
95 				op++;
96 				continue;
97 			}
98 			if (op + 1 + op[1] >= oe)
99 				break;
100 			if (op[0] == DHO_DHCP_MESSAGE_TYPE &&
101 			    op[1] == 1)
102 				dhcptype = op[2];
103 			else if (op[0] == DHO_DHCP_REQUESTED_ADDRESS &&
104 			    op[1] == sizeof(requested_addr))
105 				memcpy(&requested_addr, &op[2],
106 				    sizeof(requested_addr));
107 			op += 2 + op[1];
108 		}
109 	}
110 
111 	memset(&resp, 0, sizeof(resp));
112 	resp.op = BOOTREPLY;
113 	resp.htype = req.htype;
114 	resp.hlen = req.hlen;
115 	resp.xid = req.xid;
116 
117 	if (dev->pxeboot) {
118 		strlcpy(resp.file, "auto_install", sizeof resp.file);
119 		vm = vm_getbyvmid(dev->vm_vmid);
120 		if (vm && res_hnok(vm->vm_params.vmc_params.vcp_name))
121 			hostname = vm->vm_params.vmc_params.vcp_name;
122 	}
123 
124 	if ((client_addr.s_addr =
125 	    vm_priv_addr(&env->vmd_cfg,
126 	    dev->vm_vmid, dev->idx, 1)) == 0)
127 		return (-1);
128 	memcpy(&resp.yiaddr, &client_addr,
129 	    sizeof(client_addr));
130 	memcpy(&ss2sin(&pc.pc_dst)->sin_addr, &client_addr,
131 	    sizeof(client_addr));
132 	ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT);
133 
134 	if ((server_addr.s_addr = vm_priv_addr(&env->vmd_cfg, dev->vm_vmid,
135 	    dev->idx, 0)) == 0)
136 		return (-1);
137 	memcpy(&resp.siaddr, &server_addr, sizeof(server_addr));
138 	memcpy(&ss2sin(&pc.pc_src)->sin_addr, &server_addr,
139 	    sizeof(server_addr));
140 	ss2sin(&pc.pc_src)->sin_port = htons(SERVER_PORT);
141 
142 	/* Packet is already allocated */
143 	if (*obuf != NULL)
144 		goto fail;
145 
146 	buflen = 0;
147 	respbuflen = DHCP_MTU_MAX;
148 	if ((respbuf = calloc(1, respbuflen)) == NULL)
149 		goto fail;
150 
151 	memcpy(&pc.pc_dmac, dev->mac, sizeof(pc.pc_dmac));
152 	memcpy(&resp.chaddr, dev->mac, resp.hlen);
153 	memcpy(&pc.pc_smac, dev->mac, sizeof(pc.pc_smac));
154 	pc.pc_smac[5]++;
155 	if ((offset = assemble_hw_header(respbuf, respbuflen, 0,
156 	    &pc, HTYPE_ETHER)) < 0) {
157 		log_debug("%s: assemble_hw_header failed", __func__);
158 		goto fail;
159 	}
160 
161 	/* BOOTP uses a 64byte vendor field instead of the DHCP options */
162 	resplen = BOOTP_MIN_LEN;
163 
164 	/* Add BOOTP Vendor Extensions (DHCP options) */
165 	o = 0;
166 	memcpy(&resp.options,
167 	    DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN);
168 	o+= DHCP_OPTIONS_COOKIE_LEN;
169 
170 	/* Did we receive a DHCP request or was it just BOOTP? */
171 	if (dhcptype) {
172 		/*
173 		 * There is no need for a real state machine as we always
174 		 * answer with the same client IP and options for the VM.
175 		 */
176 		if (dhcptype == DHCPDISCOVER)
177 			dhcptype = DHCPOFFER;
178 		else if (dhcptype == DHCPREQUEST &&
179 		    (requested_addr.s_addr == 0 ||
180 		    client_addr.s_addr == requested_addr.s_addr))
181 			dhcptype = DHCPACK;
182 		else
183 			dhcptype = DHCPNAK;
184 
185 		resp.options[o++] = DHO_DHCP_MESSAGE_TYPE;
186 		resp.options[o++] = sizeof(dhcptype);
187 		memcpy(&resp.options[o], &dhcptype, sizeof(dhcptype));
188 		o += sizeof(dhcptype);
189 
190 		/* Our lease never changes, use the maximum lease time */
191 		resp.options[o++] = DHO_DHCP_LEASE_TIME;
192 		resp.options[o++] = sizeof(ltime);
193 		ltime = ntohl(0xffffffff);
194 		memcpy(&resp.options[o], &ltime, sizeof(ltime));
195 		o += sizeof(ltime);
196 
197 		resp.options[o++] = DHO_DHCP_SERVER_IDENTIFIER;
198 		resp.options[o++] = sizeof(server_addr);
199 		memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
200 		o += sizeof(server_addr);
201 	}
202 
203 	resp.options[o++] = DHO_SUBNET_MASK;
204 	resp.options[o++] = sizeof(mask);
205 	mask.s_addr = htonl(0xfffffffe);
206 	memcpy(&resp.options[o], &mask, sizeof(mask));
207 	o += sizeof(mask);
208 
209 	resp.options[o++] = DHO_ROUTERS;
210 	resp.options[o++] = sizeof(server_addr);
211 	memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
212 	o += sizeof(server_addr);
213 
214 	resp.options[o++] = DHO_DOMAIN_NAME_SERVERS;
215 	resp.options[o++] = sizeof(server_addr);
216 	memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
217 	o += sizeof(server_addr);
218 
219 	if (hostname != NULL) {
220 		len = strlen(hostname);
221 		resp.options[o++] = DHO_HOST_NAME;
222 		resp.options[o++] = len;
223 		memcpy(&resp.options[o], hostname, len);
224 		o += len;
225 	}
226 
227 	resp.options[o++] = DHO_END;
228 
229 	resplen = offsetof(struct dhcp_packet, options) + o;
230 
231 	/* Minimum packet size */
232 	if (resplen < BOOTP_MIN_LEN)
233 		resplen = BOOTP_MIN_LEN;
234 
235 	if ((offset = assemble_udp_ip_header(respbuf, respbuflen, offset, &pc,
236 	    (unsigned char *)&resp, resplen)) < 0) {
237 		log_debug("%s: assemble_udp_ip_header failed", __func__);
238 		goto fail;
239 	}
240 
241 	memcpy(respbuf + offset, &resp, resplen);
242 	respbuflen = offset + resplen;
243 
244 	*obuf = respbuf;
245 	return (respbuflen);
246  fail:
247 	free(respbuf);
248 	return (0);
249 }
250