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 | |
55 | struct 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 | */ |
69 | static 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 | */ |
133 | static 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 | */ |
206 | void 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 | |