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], <ime, 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