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