1/* SPDX-License-Identifier: BSD-3-Clause */
2/*
3 * SLIRP stateless DHCPv6
4 *
5 * We only support stateless DHCPv6, e.g. for network booting.
6 * See RFC 3315, RFC 3736, RFC 3646 and RFC 5970 for details.
7 *
8 * Copyright 2016 Thomas Huth, Red Hat Inc.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer.
17 *
18 * 2. Redistributions in binary form must reproduce the above
19 * copyright notice, this list of conditions and the following
20 * disclaimer in the documentation and/or other materials provided
21 * with the distribution.
22 *
23 * 3. Neither the name of the copyright holder nor the names of its
24 * contributors may be used to endorse or promote products derived
25 * from this software without specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
30 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
31 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
32 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
33 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
34 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
36 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
38 * OF THE POSSIBILITY OF SUCH DAMAGE.
39 */
40
41#include "slirp.h"
42#include "dhcpv6.h"
43
44/* DHCPv6 message types */
45#define MSGTYPE_REPLY 7
46#define MSGTYPE_INFO_REQUEST 11
47
48/* DHCPv6 option types */
49#define OPTION_CLIENTID 1
50#define OPTION_IAADDR 5
51#define OPTION_ORO 6
52#define OPTION_DNS_SERVERS 23
53#define OPTION_BOOTFILE_URL 59
54
55struct requested_infos {
56 uint8_t *client_id;
57 int client_id_len;
58 bool want_dns;
59 bool want_boot_url;
60};
61
62/**
63 * Analyze the info request message sent by the client to see what data it
64 * provided and what it wants to have. The information is gathered in the
65 * "requested_infos" struct. Note that client_id (if provided) points into
66 * the odata region, thus the caller must keep odata valid as long as it
67 * needs to access the requested_infos struct.
68 */
69static int dhcpv6_parse_info_request(Slirp *slirp, uint8_t *odata, int olen,
70 struct requested_infos *ri)
71{
72 int i, req_opt;
73
74 while (olen > 4) {
75 /* Parse one option */
76 int option = odata[0] << 8 | odata[1];
77 int len = odata[2] << 8 | odata[3];
78
79 if (len + 4 > olen) {
80 slirp->cb->guest_error("Guest sent bad DHCPv6 packet!",
81 slirp->opaque);
82 return -E2BIG;
83 }
84
85 switch (option) {
86 case OPTION_IAADDR:
87 /* According to RFC3315, we must discard requests with IA option */
88 return -EINVAL;
89 case OPTION_CLIENTID:
90 if (len > 256) {
91 /* Avoid very long IDs which could cause problems later */
92 return -E2BIG;
93 }
94 ri->client_id = odata + 4;
95 ri->client_id_len = len;
96 break;
97 case OPTION_ORO: /* Option request option */
98 if (len & 1) {
99 return -EINVAL;
100 }
101 /* Check which options the client wants to have */
102 for (i = 0; i < len; i += 2) {
103 req_opt = odata[4 + i] << 8 | odata[4 + i + 1];
104 switch (req_opt) {
105 case OPTION_DNS_SERVERS:
106 ri->want_dns = true;
107 break;
108 case OPTION_BOOTFILE_URL:
109 ri->want_boot_url = true;
110 break;
111 default:
112 DEBUG_MISC("dhcpv6: Unsupported option request %d",
113 req_opt);
114 }
115 }
116 break;
117 default:
118 DEBUG_MISC("dhcpv6 info req: Unsupported option %d, len=%d", option,
119 len);
120 }
121
122 odata += len + 4;
123 olen -= len + 4;
124 }
125
126 return 0;
127}
128
129
130/**
131 * Handle information request messages
132 */
133static void dhcpv6_info_request(Slirp *slirp, struct sockaddr_in6 *srcsas,
134 uint32_t xid, uint8_t *odata, int olen)
135{
136 struct requested_infos ri = { NULL };
137 struct sockaddr_in6 sa6, da6;
138 struct mbuf *m;
139 uint8_t *resp;
140
141 if (dhcpv6_parse_info_request(slirp, odata, olen, &ri) < 0) {
142 return;
143 }
144
145 m = m_get(slirp);
146 if (!m) {
147 return;
148 }
149 memset(m->m_data, 0, m->m_size);
150 m->m_data += IF_MAXLINKHDR;
151 resp = (uint8_t *)m->m_data + sizeof(struct ip6) + sizeof(struct udphdr);
152
153 /* Fill in response */
154 *resp++ = MSGTYPE_REPLY;
155 *resp++ = (uint8_t)(xid >> 16);
156 *resp++ = (uint8_t)(xid >> 8);
157 *resp++ = (uint8_t)xid;
158
159 if (ri.client_id) {
160 *resp++ = OPTION_CLIENTID >> 8; /* option-code high byte */
161 *resp++ = OPTION_CLIENTID; /* option-code low byte */
162 *resp++ = ri.client_id_len >> 8; /* option-len high byte */
163 *resp++ = ri.client_id_len; /* option-len low byte */
164 memcpy(resp, ri.client_id, ri.client_id_len);
165 resp += ri.client_id_len;
166 }
167 if (ri.want_dns) {
168 *resp++ = OPTION_DNS_SERVERS >> 8; /* option-code high byte */
169 *resp++ = OPTION_DNS_SERVERS; /* option-code low byte */
170 *resp++ = 0; /* option-len high byte */
171 *resp++ = 16; /* option-len low byte */
172 memcpy(resp, &slirp->vnameserver_addr6, 16);
173 resp += 16;
174 }
175 if (ri.want_boot_url) {
176 uint8_t *sa = slirp->vhost_addr6.s6_addr;
177 int slen, smaxlen;
178
179 *resp++ = OPTION_BOOTFILE_URL >> 8; /* option-code high byte */
180 *resp++ = OPTION_BOOTFILE_URL; /* option-code low byte */
181 smaxlen = (uint8_t *)m->m_data + IF_MTU - (resp + 2);
182 slen = snprintf((char *)resp + 2, smaxlen,
183 "tftp://[%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
184 "%02x%02x:%02x%02x:%02x%02x:%02x%02x]/%s",
185 sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7],
186 sa[8], sa[9], sa[10], sa[11], sa[12], sa[13], sa[14],
187 sa[15], slirp->bootp_filename);
188 slen = MIN(slen, smaxlen);
189 *resp++ = slen >> 8; /* option-len high byte */
190 *resp++ = slen; /* option-len low byte */
191 resp += slen;
192 }
193
194 sa6.sin6_addr = slirp->vhost_addr6;
195 sa6.sin6_port = DHCPV6_SERVER_PORT;
196 da6.sin6_addr = srcsas->sin6_addr;
197 da6.sin6_port = srcsas->sin6_port;
198 m->m_data += sizeof(struct ip6) + sizeof(struct udphdr);
199 m->m_len = resp - (uint8_t *)m->m_data;
200 udp6_output(NULL, m, &sa6, &da6);
201}
202
203/**
204 * Handle DHCPv6 messages sent by the client
205 */
206void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m)
207{
208 uint8_t *data = (uint8_t *)m->m_data + sizeof(struct udphdr);
209 int data_len = m->m_len - sizeof(struct udphdr);
210 uint32_t xid;
211
212 if (data_len < 4) {
213 return;
214 }
215
216 xid = ntohl(*(uint32_t *)data) & 0xffffff;
217
218 switch (data[0]) {
219 case MSGTYPE_INFO_REQUEST:
220 dhcpv6_info_request(m->slirp, srcsas, xid, &data[4], data_len - 4);
221 break;
222 default:
223 DEBUG_MISC("dhcpv6_input: Unsupported message type 0x%x", data[0]);
224 }
225}
226