/* * SLIRP stateless DHCPv6 * * We only support stateless DHCPv6, e.g. for network booting. * See RFC 3315, RFC 3736, RFC 3646 and RFC 5970 for details. * * Copyright 2016 Thomas Huth, Red Hat Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "qemu/osdep.h" #include "qemu/log.h" #include "slirp.h" #include "dhcpv6.h" /* DHCPv6 message types */ #define MSGTYPE_REPLY 7 #define MSGTYPE_INFO_REQUEST 11 /* DHCPv6 option types */ #define OPTION_CLIENTID 1 #define OPTION_IAADDR 5 #define OPTION_ORO 6 #define OPTION_DNS_SERVERS 23 #define OPTION_BOOTFILE_URL 59 struct requested_infos { uint8_t *client_id; int client_id_len; bool want_dns; bool want_boot_url; }; /** * Analyze the info request message sent by the client to see what data it * provided and what it wants to have. The information is gathered in the * "requested_infos" struct. Note that client_id (if provided) points into * the odata region, thus the caller must keep odata valid as long as it * needs to access the requested_infos struct. */ static int dhcpv6_parse_info_request(uint8_t *odata, int olen, struct requested_infos *ri) { int i, req_opt; while (olen > 4) { /* Parse one option */ int option = odata[0] << 8 | odata[1]; int len = odata[2] << 8 | odata[3]; if (len + 4 > olen) { qemu_log_mask(LOG_GUEST_ERROR, "Guest sent bad DHCPv6 packet!\n"); return -E2BIG; } switch (option) { case OPTION_IAADDR: /* According to RFC3315, we must discard requests with IA option */ return -EINVAL; case OPTION_CLIENTID: if (len > 256) { /* Avoid very long IDs which could cause problems later */ return -E2BIG; } ri->client_id = odata + 4; ri->client_id_len = len; break; case OPTION_ORO: /* Option request option */ if (len & 1) { return -EINVAL; } /* Check which options the client wants to have */ for (i = 0; i < len; i += 2) { req_opt = odata[4 + i] << 8 | odata[4 + i + 1]; switch (req_opt) { case OPTION_DNS_SERVERS: ri->want_dns = true; break; case OPTION_BOOTFILE_URL: ri->want_boot_url = true; break; default: DEBUG_MISC((dfd, "dhcpv6: Unsupported option request %d\n", req_opt)); } } break; default: DEBUG_MISC((dfd, "dhcpv6 info req: Unsupported option %d, len=%d\n", option, len)); } odata += len + 4; olen -= len + 4; } return 0; } /** * Handle information request messages */ static void dhcpv6_info_request(Slirp *slirp, struct sockaddr_in6 *srcsas, uint32_t xid, uint8_t *odata, int olen) { struct requested_infos ri = { NULL }; struct sockaddr_in6 sa6, da6; struct mbuf *m; uint8_t *resp; if (dhcpv6_parse_info_request(odata, olen, &ri) < 0) { return; } m = m_get(slirp); if (!m) { return; } memset(m->m_data, 0, m->m_size); m->m_data += IF_MAXLINKHDR; resp = (uint8_t *)m->m_data + sizeof(struct ip6) + sizeof(struct udphdr); /* Fill in response */ *resp++ = MSGTYPE_REPLY; *resp++ = (uint8_t)(xid >> 16); *resp++ = (uint8_t)(xid >> 8); *resp++ = (uint8_t)xid; if (ri.client_id) { *resp++ = OPTION_CLIENTID >> 8; /* option-code high byte */ *resp++ = OPTION_CLIENTID; /* option-code low byte */ *resp++ = ri.client_id_len >> 8; /* option-len high byte */ *resp++ = ri.client_id_len; /* option-len low byte */ memcpy(resp, ri.client_id, ri.client_id_len); resp += ri.client_id_len; } if (ri.want_dns) { *resp++ = OPTION_DNS_SERVERS >> 8; /* option-code high byte */ *resp++ = OPTION_DNS_SERVERS; /* option-code low byte */ *resp++ = 0; /* option-len high byte */ *resp++ = 16; /* option-len low byte */ memcpy(resp, &slirp->vnameserver_addr6, 16); resp += 16; } if (ri.want_boot_url) { uint8_t *sa = slirp->vhost_addr6.s6_addr; int slen, smaxlen; *resp++ = OPTION_BOOTFILE_URL >> 8; /* option-code high byte */ *resp++ = OPTION_BOOTFILE_URL; /* option-code low byte */ smaxlen = (uint8_t *)m->m_data + IF_MTU - (resp + 2); slen = snprintf((char *)resp + 2, smaxlen, "tftp://[%02x%02x:%02x%02x:%02x%02x:%02x%02x:" "%02x%02x:%02x%02x:%02x%02x:%02x%02x]/%s", sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7], sa[8], sa[9], sa[10], sa[11], sa[12], sa[13], sa[14], sa[15], slirp->bootp_filename); slen = min(slen, smaxlen); *resp++ = slen >> 8; /* option-len high byte */ *resp++ = slen; /* option-len low byte */ resp += slen; } sa6.sin6_addr = slirp->vhost_addr6; sa6.sin6_port = DHCPV6_SERVER_PORT; da6.sin6_addr = srcsas->sin6_addr; da6.sin6_port = srcsas->sin6_port; m->m_data += sizeof(struct ip6) + sizeof(struct udphdr); m->m_len = resp - (uint8_t *)m->m_data; udp6_output(NULL, m, &sa6, &da6); } /** * Handle DHCPv6 messages sent by the client */ void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m) { uint8_t *data = (uint8_t *)m->m_data + sizeof(struct udphdr); int data_len = m->m_len - sizeof(struct udphdr); uint32_t xid; if (data_len < 4) { return; } xid = ntohl(*(uint32_t *)data) & 0xffffff; switch (data[0]) { case MSGTYPE_INFO_REQUEST: dhcpv6_info_request(m->slirp, srcsas, xid, &data[4], data_len - 4); break; default: DEBUG_MISC((dfd, "dhcpv6_input: Unsupported message type 0x%x\n", data[0])); } }