xref: /openbsd-src/usr.sbin/vmd/dhcp.c (revision 46035553bfdd96e63c94e32da0210227ec2e3cf1)
1 /*	$OpenBSD: dhcp.c,v 1.8 2018/12/27 19:51:30 anton 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_smac, dev->mac, ETHER_ADDR_LEN) != 0 ||
61 	    memcmp(pc.pc_dmac, broadcast, ETHER_ADDR_LEN) != 0)
62 		return (-1);
63 
64 	if ((offset = decode_udp_ip_header(buf, buflen, offset, &pc)) < 0)
65 		return (-1);
66 
67 	if (ntohs(ss2sin(&pc.pc_src)->sin_port) != CLIENT_PORT ||
68 	    ntohs(ss2sin(&pc.pc_dst)->sin_port) != SERVER_PORT)
69 		return (-1);
70 
71 	memset(&req, 0, sizeof(req));
72 	memcpy(&req, buf + offset, buflen - offset);
73 
74 	if (req.op != BOOTREQUEST ||
75 	    req.htype != pc.pc_htype ||
76 	    req.hlen != ETHER_ADDR_LEN ||
77 	    memcmp(dev->mac, req.chaddr, req.hlen) != 0)
78 		return (-1);
79 
80 	/* Ignore unsupported requests for now */
81 	if (req.ciaddr.s_addr != 0 || req.file[0] != '\0' || req.hops != 0)
82 		return (-1);
83 
84 	/* Get a few DHCP options (best effort as we fall back to BOOTP) */
85 	if (memcmp(&req.options,
86 	    DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN) == 0) {
87 		memset(&requested_addr, 0, sizeof(requested_addr));
88 		op = req.options + DHCP_OPTIONS_COOKIE_LEN;
89 		oe = req.options + sizeof(req.options);
90 		while (*op != DHO_END && op < oe) {
91 			if (op[0] == DHO_PAD) {
92 				op++;
93 				continue;
94 			}
95 			if (op + 1 + op[1] >= oe)
96 				break;
97 			if (op[0] == DHO_DHCP_MESSAGE_TYPE &&
98 			    op[1] == 1)
99 				dhcptype = op[2];
100 			else if (op[0] == DHO_DHCP_REQUESTED_ADDRESS &&
101 			    op[1] == sizeof(requested_addr))
102 				memcpy(&requested_addr, &op[2],
103 				    sizeof(requested_addr));
104 			op += 2 + op[1];
105 		}
106 	}
107 
108 	memset(&resp, 0, sizeof(resp));
109 	resp.op = BOOTREPLY;
110 	resp.htype = req.htype;
111 	resp.hlen = req.hlen;
112 	resp.xid = req.xid;
113 
114 	if (dev->pxeboot) {
115 		strlcpy(resp.file, "auto_install", sizeof resp.file);
116 		vm = vm_getbyvmid(dev->vm_vmid);
117 		if (vm && res_hnok(vm->vm_params.vmc_params.vcp_name))
118 			hostname = vm->vm_params.vmc_params.vcp_name;
119 	}
120 
121 	if ((client_addr.s_addr =
122 	    vm_priv_addr(&env->vmd_cfg,
123 	    dev->vm_vmid, dev->idx, 1)) == 0)
124 		return (-1);
125 	memcpy(&resp.yiaddr, &client_addr,
126 	    sizeof(client_addr));
127 	memcpy(&ss2sin(&pc.pc_dst)->sin_addr, &client_addr,
128 	    sizeof(client_addr));
129 	ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT);
130 
131 	if ((server_addr.s_addr = vm_priv_addr(&env->vmd_cfg, dev->vm_vmid,
132 	    dev->idx, 0)) == 0)
133 		return (-1);
134 	memcpy(&resp.siaddr, &server_addr, sizeof(server_addr));
135 	memcpy(&ss2sin(&pc.pc_src)->sin_addr, &server_addr,
136 	    sizeof(server_addr));
137 	ss2sin(&pc.pc_src)->sin_port = htons(SERVER_PORT);
138 
139 	/* Packet is already allocated */
140 	if (*obuf != NULL)
141 		goto fail;
142 
143 	buflen = 0;
144 	respbuflen = DHCP_MTU_MAX;
145 	if ((respbuf = calloc(1, respbuflen)) == NULL)
146 		goto fail;
147 
148 	memcpy(&pc.pc_dmac, dev->mac, sizeof(pc.pc_dmac));
149 	memcpy(&resp.chaddr, dev->mac, resp.hlen);
150 	memcpy(&pc.pc_smac, dev->mac, sizeof(pc.pc_smac));
151 	pc.pc_smac[5]++;
152 	if ((offset = assemble_hw_header(respbuf, respbuflen, 0,
153 	    &pc, HTYPE_ETHER)) < 0) {
154 		log_debug("%s: assemble_hw_header failed", __func__);
155 		goto fail;
156 	}
157 
158 	/* BOOTP uses a 64byte vendor field instead of the DHCP options */
159 	resplen = BOOTP_MIN_LEN;
160 
161 	/* Add BOOTP Vendor Extensions (DHCP options) */
162 	o = 0;
163 	memcpy(&resp.options,
164 	    DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN);
165 	o+= DHCP_OPTIONS_COOKIE_LEN;
166 
167 	/* Did we receive a DHCP request or was it just BOOTP? */
168 	if (dhcptype) {
169 		/*
170 		 * There is no need for a real state machine as we always
171 		 * answer with the same client IP and options for the VM.
172 		 */
173 		if (dhcptype == DHCPDISCOVER)
174 			dhcptype = DHCPOFFER;
175 		else if (dhcptype == DHCPREQUEST &&
176 		    (requested_addr.s_addr == 0 ||
177 		    client_addr.s_addr == requested_addr.s_addr))
178 			dhcptype = DHCPACK;
179 		else
180 			dhcptype = DHCPNAK;
181 
182 		resp.options[o++] = DHO_DHCP_MESSAGE_TYPE;
183 		resp.options[o++] = sizeof(dhcptype);
184 		memcpy(&resp.options[o], &dhcptype, sizeof(dhcptype));
185 		o += sizeof(dhcptype);
186 
187 		/* Our lease never changes, use the maximum lease time */
188 		resp.options[o++] = DHO_DHCP_LEASE_TIME;
189 		resp.options[o++] = sizeof(ltime);
190 		ltime = ntohl(0xffffffff);
191 		memcpy(&resp.options[o], &ltime, sizeof(ltime));
192 		o += sizeof(ltime);
193 
194 		resp.options[o++] = DHO_DHCP_SERVER_IDENTIFIER;
195 		resp.options[o++] = sizeof(server_addr);
196 		memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
197 		o += sizeof(server_addr);
198 	}
199 
200 	resp.options[o++] = DHO_SUBNET_MASK;
201 	resp.options[o++] = sizeof(mask);
202 	mask.s_addr = htonl(0xfffffffe);
203 	memcpy(&resp.options[o], &mask, sizeof(mask));
204 	o += sizeof(mask);
205 
206 	resp.options[o++] = DHO_ROUTERS;
207 	resp.options[o++] = sizeof(server_addr);
208 	memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
209 	o += sizeof(server_addr);
210 
211 	resp.options[o++] = DHO_DOMAIN_NAME_SERVERS;
212 	resp.options[o++] = sizeof(server_addr);
213 	memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
214 	o += sizeof(server_addr);
215 
216 	if (hostname != NULL) {
217 		len = strlen(hostname);
218 		resp.options[o++] = DHO_HOST_NAME;
219 		resp.options[o++] = len;
220 		memcpy(&resp.options[o], hostname, len);
221 		o += len;
222 	}
223 
224 	resp.options[o++] = DHO_END;
225 
226 	resplen = offsetof(struct dhcp_packet, options) + o;
227 
228 	/* Minimum packet size */
229 	if (resplen < BOOTP_MIN_LEN)
230 		resplen = BOOTP_MIN_LEN;
231 
232 	if ((offset = assemble_udp_ip_header(respbuf, respbuflen, offset, &pc,
233 	    (unsigned char *)&resp, resplen)) < 0) {
234 		log_debug("%s: assemble_udp_ip_header failed", __func__);
235 		goto fail;
236 	}
237 
238 	memcpy(respbuf + offset, &resp, resplen);
239 	respbuflen = offset + resplen;
240 
241 	*obuf = respbuf;
242 	return (respbuflen);
243  fail:
244 	free(respbuf);
245 	return (0);
246 }
247