GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/vmd/dhcp.c Lines: 0 115 0.0 %
Date: 2017-11-13 Branches: 0 68 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: dhcp.c,v 1.4 2017/11/05 20:01:09 reyk 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 <stdlib.h>
28
#include <string.h>
29
#include <stddef.h>
30
31
#include "proc.h"
32
#include "vmd.h"
33
#include "dhcp.h"
34
#include "virtio.h"
35
36
static const uint8_t broadcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
37
extern struct vmd *env;
38
39
ssize_t
40
dhcp_request(struct vionet_dev *dev, char *buf, size_t buflen, char **obuf)
41
{
42
	unsigned char		*respbuf = NULL, *op, *oe, dhcptype = 0;
43
	ssize_t			 offset, respbuflen = 0;
44
	struct packet_ctx	 pc;
45
	struct dhcp_packet	 req, resp;
46
	struct in_addr		 server_addr, mask, client_addr, requested_addr;
47
	size_t			 resplen, o;
48
	uint32_t		 ltime;
49
50
	if (buflen < (ssize_t)(BOOTP_MIN_LEN + sizeof(struct ether_header)))
51
		return (-1);
52
53
	memset(&pc, 0, sizeof(pc));
54
	if ((offset = decode_hw_header(buf, buflen, 0, &pc, HTYPE_ETHER)) < 0)
55
		return (-1);
56
57
	if (memcmp(pc.pc_smac, dev->mac, ETHER_ADDR_LEN) != 0 ||
58
	    memcmp(pc.pc_dmac, broadcast, ETHER_ADDR_LEN) != 0)
59
		return (-1);
60
61
	if ((offset = decode_udp_ip_header(buf, buflen, offset, &pc)) < 0)
62
		return (-1);
63
64
	if (ntohs(ss2sin(&pc.pc_src)->sin_port) != CLIENT_PORT ||
65
	    ntohs(ss2sin(&pc.pc_dst)->sin_port) != SERVER_PORT)
66
		return (-1);
67
68
	memset(&req, 0, sizeof(req));
69
	memcpy(&req, buf + offset, buflen - offset);
70
71
	if (req.op != BOOTREQUEST ||
72
	    req.htype != pc.pc_htype ||
73
	    req.hlen != ETHER_ADDR_LEN ||
74
	    memcmp(dev->mac, req.chaddr, req.hlen) != 0)
75
		return (-1);
76
77
	/* Ignore unsupported requests for now */
78
	if (req.ciaddr.s_addr != 0 || req.file[0] != '\0' || req.hops != 0)
79
		return (-1);
80
81
	/* Get a few DHCP options (best effort as we fall back to BOOTP) */
82
	if (memcmp(&req.options,
83
	    DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN) == 0) {
84
		memset(&requested_addr, 0, sizeof(requested_addr));
85
		op = req.options + DHCP_OPTIONS_COOKIE_LEN;
86
		oe = req.options + sizeof(req.options);
87
		while (*op != DHO_END && op < oe) {
88
			if (op[0] == DHO_PAD) {
89
				op++;
90
				continue;
91
			}
92
			if (op + 1 + op[1] >= oe)
93
				break;
94
			if (op[0] == DHO_DHCP_MESSAGE_TYPE &&
95
			    op[1] == 1)
96
				dhcptype = op[2];
97
			else if (op[0] == DHO_DHCP_REQUESTED_ADDRESS &&
98
			    op[1] == sizeof(requested_addr))
99
				memcpy(&requested_addr, &op[2],
100
				    sizeof(requested_addr));
101
			op += 2 + op[1];
102
		}
103
	}
104
105
	memset(&resp, 0, sizeof(resp));
106
	resp.op = BOOTREPLY;
107
	resp.htype = req.htype;
108
	resp.hlen = req.hlen;
109
	resp.xid = req.xid;
110
111
	if ((client_addr.s_addr =
112
	    vm_priv_addr(&env->vmd_cfg.cfg_localprefix,
113
	    dev->vm_vmid, dev->idx, 1)) == 0)
114
		return (-1);
115
	memcpy(&resp.yiaddr, &client_addr,
116
	    sizeof(client_addr));
117
	memcpy(&ss2sin(&pc.pc_dst)->sin_addr, &client_addr,
118
	    sizeof(client_addr));
119
	ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT);
120
121
	if ((server_addr.s_addr =
122
	    vm_priv_addr(&env->vmd_cfg.cfg_localprefix,
123
	    dev->vm_vmid, dev->idx, 0)) == 0)
124
		return (-1);
125
	memcpy(&resp.siaddr, &server_addr,
126
	    sizeof(server_addr));
127
	memcpy(&ss2sin(&pc.pc_src)->sin_addr, &server_addr,
128
	    sizeof(server_addr));
129
	ss2sin(&pc.pc_src)->sin_port = htons(SERVER_PORT);
130
131
	/* Packet is already allocated */
132
	if (*obuf != NULL)
133
		goto fail;
134
135
	buflen = 0;
136
	respbuflen = DHCP_MTU_MAX;
137
	if ((respbuf = calloc(1, respbuflen)) == NULL)
138
		goto fail;
139
140
	memcpy(&pc.pc_dmac, dev->mac, sizeof(pc.pc_dmac));
141
	memcpy(&resp.chaddr, dev->mac, resp.hlen);
142
	memcpy(&pc.pc_smac, dev->mac, sizeof(pc.pc_smac));
143
	pc.pc_smac[5]++;
144
	if ((offset = assemble_hw_header(respbuf, respbuflen, 0,
145
	    &pc, HTYPE_ETHER)) < 0) {
146
		log_debug("%s: assemble_hw_header failed", __func__);
147
		goto fail;
148
	}
149
150
	/* BOOTP uses a 64byte vendor field instead of the DHCP options */
151
	resplen = BOOTP_MIN_LEN;
152
153
	/* Add BOOTP Vendor Extensions (DHCP options) */
154
	o = 0;
155
	memcpy(&resp.options,
156
	    DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN);
157
	o+= DHCP_OPTIONS_COOKIE_LEN;
158
159
	/* Did we receive a DHCP request or was it just BOOTP? */
160
	if (dhcptype) {
161
		/*
162
		 * There is no need for a real state machine as we always
163
		 * answer with the same client IP and options for the VM.
164
		 */
165
		if (dhcptype == DHCPDISCOVER)
166
			dhcptype = DHCPOFFER;
167
		else if (dhcptype == DHCPREQUEST &&
168
		    (requested_addr.s_addr == 0 ||
169
		    client_addr.s_addr == requested_addr.s_addr))
170
			dhcptype = DHCPACK;
171
		else
172
			dhcptype = DHCPNAK;
173
174
		resp.options[o++] = DHO_DHCP_MESSAGE_TYPE;
175
		resp.options[o++] = sizeof(dhcptype);
176
		memcpy(&resp.options[o], &dhcptype, sizeof(dhcptype));
177
		o += sizeof(dhcptype);
178
179
		/* Our lease never changes, use the maximum lease time */
180
		resp.options[o++] = DHO_DHCP_LEASE_TIME;
181
		resp.options[o++] = sizeof(ltime);
182
		ltime = ntohl(0xffffffff);
183
		memcpy(&resp.options[o], &ltime, sizeof(ltime));
184
		o += sizeof(ltime);
185
186
		resp.options[o++] = DHO_DHCP_SERVER_IDENTIFIER;
187
		resp.options[o++] = sizeof(server_addr);
188
		memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
189
		o += sizeof(server_addr);
190
	}
191
192
	resp.options[o++] = DHO_DOMAIN_NAME_SERVERS;
193
	resp.options[o++] = sizeof(server_addr);
194
	memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
195
	o += sizeof(server_addr);
196
197
	resp.options[o++] = DHO_SUBNET_MASK;
198
	resp.options[o++] = sizeof(mask);
199
	mask.s_addr = htonl(0xfffffffe);
200
	memcpy(&resp.options[o], &mask, sizeof(mask));
201
	o += sizeof(mask);
202
203
	resp.options[o++] = DHO_ROUTERS;
204
	resp.options[o++] = sizeof(server_addr);
205
	memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
206
	o += sizeof(server_addr);
207
208
	resp.options[o++] = DHO_DOMAIN_NAME_SERVERS;
209
	resp.options[o++] = sizeof(server_addr);
210
	memcpy(&resp.options[o], &server_addr, sizeof(server_addr));
211
	o += sizeof(server_addr);
212
213
	resp.options[o++] = DHO_END;
214
215
	resplen = offsetof(struct dhcp_packet, options) + o;
216
217
	/* Minimum packet size */
218
	if (resplen < BOOTP_MIN_LEN)
219
		resplen = BOOTP_MIN_LEN;
220
221
	if ((offset = assemble_udp_ip_header(respbuf, respbuflen, offset, &pc,
222
	    (unsigned char *)&resp, resplen)) < 0) {
223
		log_debug("%s: assemble_udp_ip_header failed", __func__);
224
		goto fail;
225
	}
226
227
	memcpy(respbuf + offset, &resp, resplen);
228
	respbuflen = offset + resplen;
229
230
	*obuf = respbuf;
231
	return (respbuflen);
232
 fail:
233
	free(respbuf);
234
	return (0);
235
}