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