xref: /openbsd/usr.sbin/vmd/dhcp.c (revision 5a38ef86)
1 /*	$OpenBSD: dhcp.c,v 1.11 2021/06/16 16:55:02 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/ip.h>
25 #include <netinet/udp.h>
26 #include <netinet/if_ether.h>
27 #include <arpa/inet.h>
28 
29 #include <resolv.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <stddef.h>
33 
34 #include "dhcp.h"
35 #include "virtio.h"
36 #include "vmd.h"
37 
38 #define OPTIONS_OFFSET	offsetof(struct dhcp_packet, options)
39 #define OPTIONS_MAX_LEN	\
40 	(1500 - sizeof(struct ip) - sizeof(struct udphdr) - OPTIONS_OFFSET)
41 
42 static const uint8_t broadcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
43 extern struct vmd *env;
44 
45 ssize_t
46 dhcp_request(struct vionet_dev *dev, char *buf, size_t buflen, char **obuf)
47 {
48 	unsigned char		*respbuf = NULL, *op, *oe, dhcptype = 0;
49 	unsigned char		*opts = NULL;
50 	ssize_t			 offset, optslen, respbuflen = 0;
51 	struct packet_ctx	 pc;
52 	struct dhcp_packet	 req, resp;
53 	struct in_addr		 server_addr, mask, client_addr, requested_addr;
54 	size_t			 len, resplen, o;
55 	uint32_t		 ltime;
56 	struct vmd_vm		*vm;
57 	const char		*hostname = NULL;
58 
59 	if (buflen < BOOTP_MIN_LEN + ETHER_HDR_LEN ||
60 	    buflen > 1500 + ETHER_HDR_LEN)
61 		return (-1);
62 
63 	memset(&pc, 0, sizeof(pc));
64 	if ((offset = decode_hw_header(buf, buflen, 0, &pc, HTYPE_ETHER)) < 0)
65 		return (-1);
66 
67 	if (memcmp(pc.pc_dmac, broadcast, ETHER_ADDR_LEN) != 0 &&
68 	    memcmp(pc.pc_dmac, dev->hostmac, ETHER_ADDR_LEN) != 0)
69 		return (-1);
70 
71 	if (memcmp(pc.pc_smac, dev->mac, ETHER_ADDR_LEN) != 0)
72 		return (-1);
73 
74 	if ((offset = decode_udp_ip_header(buf, buflen, offset, &pc)) < 0)
75 		return (-1);
76 
77 	if (ntohs(ss2sin(&pc.pc_src)->sin_port) != CLIENT_PORT ||
78 	    ntohs(ss2sin(&pc.pc_dst)->sin_port) != SERVER_PORT)
79 		return (-1);
80 
81 	/* Only populate the base DHCP fields. Options are parsed separately. */
82 	if ((size_t)offset + OPTIONS_OFFSET > buflen)
83 		return (-1);
84 	memset(&req, 0, sizeof(req));
85 	memcpy(&req, buf + offset, OPTIONS_OFFSET);
86 
87 	if (req.op != BOOTREQUEST ||
88 	    req.htype != pc.pc_htype ||
89 	    req.hlen != ETHER_ADDR_LEN ||
90 	    memcmp(dev->mac, req.chaddr, req.hlen) != 0)
91 		return (-1);
92 
93 	/* Ignore unsupported requests for now */
94 	if (req.ciaddr.s_addr != 0 || req.file[0] != '\0' || req.hops != 0)
95 		return (-1);
96 
97 	/*
98 	 * If packet has data that could be DHCP options, check for the cookie
99 	 * and then see if the region is still long enough to contain at least
100 	 * one variable length option (3 bytes). If not, fallback to BOOTP.
101 	 */
102 	optslen = buflen - offset - OPTIONS_OFFSET;
103 	if (optslen > DHCP_OPTIONS_COOKIE_LEN + 3 &&
104 	    optslen < (ssize_t)OPTIONS_MAX_LEN) {
105 		opts = buf + offset + OPTIONS_OFFSET;
106 
107 		if (memcmp(opts, DHCP_OPTIONS_COOKIE,
108 			DHCP_OPTIONS_COOKIE_LEN) == 0) {
109 			memset(&requested_addr, 0, sizeof(requested_addr));
110 			op = opts + DHCP_OPTIONS_COOKIE_LEN;
111 			oe = opts + optslen;
112 			while (*op != DHO_END && op + 1 < oe) {
113 				if (op[0] == DHO_PAD) {
114 					op++;
115 					continue;
116 				}
117 				if (op + 2 + op[1] > oe)
118 					break;
119 				if (op[0] == DHO_DHCP_MESSAGE_TYPE &&
120 				    op[1] == 1)
121 					dhcptype = op[2];
122 				else if (op[0] == DHO_DHCP_REQUESTED_ADDRESS &&
123 				    op[1] == sizeof(requested_addr))
124 					memcpy(&requested_addr, &op[2],
125 					    sizeof(requested_addr));
126 				op += 2 + op[1];
127 			}
128 		}
129 	}
130 
131 	memset(&resp, 0, sizeof(resp));
132 	resp.op = BOOTREPLY;
133 	resp.htype = req.htype;
134 	resp.hlen = req.hlen;
135 	resp.xid = req.xid;
136 
137 	if (dev->pxeboot) {
138 		strlcpy(resp.file, "auto_install", sizeof resp.file);
139 		vm = vm_getbyvmid(dev->vm_vmid);
140 		if (vm && res_hnok(vm->vm_params.vmc_params.vcp_name))
141 			hostname = vm->vm_params.vmc_params.vcp_name;
142 	}
143 
144 	if ((client_addr.s_addr =
145 	    vm_priv_addr(&env->vmd_cfg,
146 	    dev->vm_vmid, dev->idx, 1)) == 0)
147 		return (-1);
148 	memcpy(&resp.yiaddr, &client_addr,
149 	    sizeof(client_addr));
150 	memcpy(&ss2sin(&pc.pc_dst)->sin_addr, &client_addr,
151 	    sizeof(client_addr));
152 	ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT);
153 
154 	if ((server_addr.s_addr = vm_priv_addr(&env->vmd_cfg, dev->vm_vmid,
155 	    dev->idx, 0)) == 0)
156 		return (-1);
157 	memcpy(&resp.siaddr, &server_addr, sizeof(server_addr));
158 	memcpy(&ss2sin(&pc.pc_src)->sin_addr, &server_addr,
159 	    sizeof(server_addr));
160 	ss2sin(&pc.pc_src)->sin_port = htons(SERVER_PORT);
161 
162 	/* Packet is already allocated */
163 	if (*obuf != NULL)
164 		goto fail;
165 
166 	respbuflen = sizeof(resp);
167 	if ((respbuf = calloc(1, respbuflen)) == NULL)
168 		goto fail;
169 
170 	memcpy(&pc.pc_dmac, dev->mac, sizeof(pc.pc_dmac));
171 	memcpy(&resp.chaddr, dev->mac, resp.hlen);
172 	memcpy(&pc.pc_smac, dev->mac, sizeof(pc.pc_smac));
173 	pc.pc_smac[5]++;
174 	if ((offset = assemble_hw_header(respbuf, respbuflen, 0,
175 	    &pc, HTYPE_ETHER)) < 0) {
176 		log_debug("%s: assemble_hw_header failed", __func__);
177 		goto fail;
178 	}
179 
180 	/* Add BOOTP Vendor Extensions (DHCP options) */
181 	memcpy(&resp.options, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN);
182 	o = DHCP_OPTIONS_COOKIE_LEN;
183 
184 	/* Did we receive a DHCP request or was it just BOOTP? */
185 	if (dhcptype) {
186 		/*
187 		 * There is no need for a real state machine as we always
188 		 * answer with the same client IP and options for the VM.
189 		 */
190 		if (dhcptype == DHCPDISCOVER)
191 			dhcptype = DHCPOFFER;
192 		else if (dhcptype == DHCPREQUEST &&
193 		    (requested_addr.s_addr == 0 ||
194 		    client_addr.s_addr == requested_addr.s_addr))
195 			dhcptype = DHCPACK;
196 		else
197 			dhcptype = DHCPNAK;
198 
199 		resp.options[o++] = DHO_DHCP_MESSAGE_TYPE;
200 		resp.options[o++] = sizeof(dhcptype);
201 		memcpy(&resp.options[o], &dhcptype, sizeof(dhcptype));
202 		o += sizeof(dhcptype);
203 
204 		/* Our lease never changes, use the maximum lease time */
205 		resp.options[o++] = DHO_DHCP_LEASE_TIME;
206 		resp.options[o++] = sizeof(ltime);
207 		ltime = ntohl(0xffffffff);
208 		memcpy(&resp.options[o], &ltime, sizeof(ltime));
209 		o += sizeof(ltime);
210 
211 		resp.options[o++] = DHO_DHCP_SERVER_IDENTIFIER;
212 		resp.options[o++] = sizeof(server_addr);
213 		memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
214 		o += sizeof(server_addr);
215 	}
216 
217 	resp.options[o++] = DHO_SUBNET_MASK;
218 	resp.options[o++] = sizeof(mask);
219 	mask.s_addr = htonl(0xfffffffe);
220 	memcpy(&resp.options[o], &mask, sizeof(mask));
221 	o += sizeof(mask);
222 
223 	resp.options[o++] = DHO_ROUTERS;
224 	resp.options[o++] = sizeof(server_addr);
225 	memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
226 	o += sizeof(server_addr);
227 
228 	resp.options[o++] = DHO_DOMAIN_NAME_SERVERS;
229 	resp.options[o++] = sizeof(server_addr);
230 	memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
231 	o += sizeof(server_addr);
232 
233 	if (hostname != NULL && (len = strlen(hostname)) > 1) {
234 		/* Check if there's still room for the option type and len (2),
235 		 * hostname, and a final to-be-added DHO_END (1). */
236 		if (o + 2 + len + 1 > sizeof(resp.options)) {
237 			log_debug("%s: hostname too long", __func__);
238 			goto fail;
239 		}
240 		resp.options[o++] = DHO_HOST_NAME;
241 		resp.options[o++] = len;
242 		memcpy(&resp.options[o], hostname, len);
243 		o += len;
244 	}
245 
246 	resp.options[o++] = DHO_END;
247 
248 	resplen = OPTIONS_OFFSET + o;
249 
250 	/* Minimum packet size */
251 	if (resplen < BOOTP_MIN_LEN)
252 		resplen = BOOTP_MIN_LEN;
253 
254 	if ((offset = assemble_udp_ip_header(respbuf, respbuflen, offset, &pc,
255 	    (unsigned char *)&resp, resplen)) < 0) {
256 		log_debug("%s: assemble_udp_ip_header failed", __func__);
257 		goto fail;
258 	}
259 
260 	memcpy(respbuf + offset, &resp, resplen);
261 	respbuflen = offset + resplen;
262 
263 	*obuf = respbuf;
264 	return (respbuflen);
265  fail:
266 	free(respbuf);
267 	return (-1);
268 }
269