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