]>
Commit | Line | Data |
---|---|---|
f12ed3bf PF |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright (C) 2014 Intel Corporation. All rights reserved. | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #include <netinet/in.h> | |
23 | #include <errno.h> | |
24 | #include <string.h> | |
25 | ||
26 | #include "sparse-endian.h" | |
4903a73c | 27 | #include "unaligned.h" |
f12ed3bf PF |
28 | #include "util.h" |
29 | ||
30 | #include "dhcp6-internal.h" | |
31 | #include "dhcp6-protocol.h" | |
32 | ||
f12ed3bf PF |
33 | #define DHCP6_OPTION_IA_NA_LEN 12 |
34 | #define DHCP6_OPTION_IA_TA_LEN 4 | |
f12ed3bf | 35 | |
4903a73c TG |
36 | typedef struct DHCP6Option { |
37 | be16_t code; | |
38 | be16_t len; | |
39 | uint8_t data[]; | |
c962cb68 | 40 | } _packed_ DHCP6Option; |
4903a73c | 41 | |
f12ed3bf PF |
42 | static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, |
43 | size_t optlen) { | |
c962cb68 | 44 | DHCP6Option *option = (DHCP6Option*) *buf; |
4903a73c | 45 | |
f12ed3bf PF |
46 | assert_return(buf, -EINVAL); |
47 | assert_return(*buf, -EINVAL); | |
48 | assert_return(buflen, -EINVAL); | |
49 | ||
4903a73c | 50 | if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option)) |
f12ed3bf PF |
51 | return -ENOBUFS; |
52 | ||
c962cb68 TG |
53 | option->code = htobe16(optcode); |
54 | option->len = htobe16(optlen); | |
f12ed3bf | 55 | |
4903a73c TG |
56 | *buf += sizeof(DHCP6Option); |
57 | *buflen -= sizeof(DHCP6Option); | |
f12ed3bf PF |
58 | |
59 | return 0; | |
60 | } | |
61 | ||
62 | int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, | |
63 | size_t optlen, const void *optval) { | |
64 | int r; | |
65 | ||
ed6ee219 | 66 | assert_return(optval || optlen == 0, -EINVAL); |
f12ed3bf PF |
67 | |
68 | r = option_append_hdr(buf, buflen, code, optlen); | |
69 | if (r < 0) | |
70 | return r; | |
71 | ||
ed6ee219 PF |
72 | if (optval) |
73 | memcpy(*buf, optval, optlen); | |
f12ed3bf PF |
74 | |
75 | *buf += optlen; | |
76 | *buflen -= optlen; | |
77 | ||
78 | return 0; | |
79 | } | |
80 | ||
81 | int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) { | |
82 | uint16_t len; | |
83 | uint8_t *ia_hdr; | |
84 | size_t ia_buflen, ia_addrlen = 0; | |
85 | DHCP6Address *addr; | |
86 | int r; | |
87 | ||
88 | assert_return(buf && *buf && buflen && ia, -EINVAL); | |
89 | ||
90 | switch (ia->type) { | |
91 | case DHCP6_OPTION_IA_NA: | |
92 | len = DHCP6_OPTION_IA_NA_LEN; | |
93 | break; | |
94 | ||
95 | case DHCP6_OPTION_IA_TA: | |
96 | len = DHCP6_OPTION_IA_TA_LEN; | |
97 | break; | |
98 | ||
99 | default: | |
100 | return -EINVAL; | |
101 | } | |
102 | ||
103 | if (*buflen < len) | |
104 | return -ENOBUFS; | |
105 | ||
106 | ia_hdr = *buf; | |
107 | ia_buflen = *buflen; | |
108 | ||
4903a73c TG |
109 | *buf += sizeof(DHCP6Option); |
110 | *buflen -= sizeof(DHCP6Option); | |
f12ed3bf PF |
111 | |
112 | memcpy(*buf, &ia->id, len); | |
113 | ||
114 | *buf += len; | |
115 | *buflen -= len; | |
116 | ||
117 | LIST_FOREACH(addresses, addr, ia->addresses) { | |
118 | r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR, | |
ee3a5027 | 119 | sizeof(addr->iaaddr)); |
f12ed3bf PF |
120 | if (r < 0) |
121 | return r; | |
122 | ||
ee3a5027 | 123 | memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr)); |
f12ed3bf | 124 | |
ee3a5027 PF |
125 | *buf += sizeof(addr->iaaddr); |
126 | *buflen -= sizeof(addr->iaaddr); | |
f12ed3bf | 127 | |
4903a73c | 128 | ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr); |
f12ed3bf PF |
129 | } |
130 | ||
131 | r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen); | |
132 | if (r < 0) | |
133 | return r; | |
134 | ||
135 | return 0; | |
136 | } | |
137 | ||
c6affce8 | 138 | |
c962cb68 TG |
139 | static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) { |
140 | DHCP6Option *option = (DHCP6Option*) *buf; | |
c6affce8 PF |
141 | uint16_t len; |
142 | ||
143 | assert_return(buf, -EINVAL); | |
4903a73c | 144 | assert_return(optcode, -EINVAL); |
c6affce8 PF |
145 | assert_return(optlen, -EINVAL); |
146 | ||
4903a73c | 147 | if (*buflen < sizeof(DHCP6Option)) |
c6affce8 PF |
148 | return -ENOMSG; |
149 | ||
c962cb68 | 150 | len = be16toh(option->len); |
c6affce8 PF |
151 | |
152 | if (len > *buflen) | |
153 | return -ENOMSG; | |
154 | ||
c962cb68 | 155 | *optcode = be16toh(option->code); |
c6affce8 PF |
156 | *optlen = len; |
157 | ||
158 | *buf += 4; | |
159 | *buflen -= 4; | |
160 | ||
161 | return 0; | |
162 | } | |
163 | ||
f12ed3bf PF |
164 | int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, |
165 | size_t *optlen, uint8_t **optvalue) { | |
c6affce8 | 166 | int r; |
f12ed3bf | 167 | |
c6affce8 | 168 | assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL); |
f12ed3bf | 169 | |
c6affce8 PF |
170 | r = option_parse_hdr(buf, buflen, optcode, optlen); |
171 | if (r < 0) | |
172 | return r; | |
f12ed3bf | 173 | |
c6affce8 | 174 | if (*optlen > *buflen) |
f12ed3bf PF |
175 | return -ENOBUFS; |
176 | ||
c6affce8 PF |
177 | *optvalue = *buf; |
178 | *buflen -= *optlen; | |
179 | *buf += *optlen; | |
f12ed3bf PF |
180 | |
181 | return 0; | |
182 | } | |
c6affce8 PF |
183 | |
184 | int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, | |
185 | DHCP6IA *ia) { | |
186 | int r; | |
187 | uint16_t opt, status; | |
188 | size_t optlen; | |
189 | size_t iaaddr_offset; | |
190 | DHCP6Address *addr; | |
191 | uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0; | |
192 | ||
193 | assert_return(ia, -EINVAL); | |
194 | assert_return(!ia->addresses, -EINVAL); | |
195 | ||
196 | switch (iatype) { | |
197 | case DHCP6_OPTION_IA_NA: | |
198 | ||
4903a73c | 199 | if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) + |
ee3a5027 | 200 | sizeof(addr->iaaddr)) { |
c6affce8 PF |
201 | r = -ENOBUFS; |
202 | goto error; | |
203 | } | |
204 | ||
205 | iaaddr_offset = DHCP6_OPTION_IA_NA_LEN; | |
206 | memcpy(&ia->id, *buf, iaaddr_offset); | |
207 | ||
208 | lt_t1 = be32toh(ia->lifetime_t1); | |
209 | lt_t2 = be32toh(ia->lifetime_t2); | |
210 | ||
211 | if (lt_t1 && lt_t2 && lt_t1 > lt_t2) { | |
212 | log_dhcp6_client(client, "IA T1 %ds > T2 %ds", | |
213 | lt_t1, lt_t2); | |
214 | r = -EINVAL; | |
215 | goto error; | |
216 | } | |
217 | ||
218 | break; | |
219 | ||
220 | case DHCP6_OPTION_IA_TA: | |
4903a73c | 221 | if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) + |
ee3a5027 | 222 | sizeof(addr->iaaddr)) { |
c6affce8 PF |
223 | r = -ENOBUFS; |
224 | goto error; | |
225 | } | |
226 | ||
227 | iaaddr_offset = DHCP6_OPTION_IA_TA_LEN; | |
228 | memcpy(&ia->id, *buf, iaaddr_offset); | |
229 | ||
230 | ia->lifetime_t1 = 0; | |
231 | ia->lifetime_t2 = 0; | |
232 | ||
233 | break; | |
234 | ||
235 | default: | |
236 | r = -ENOMSG; | |
237 | goto error; | |
238 | } | |
239 | ||
240 | ia->type = iatype; | |
241 | ||
242 | *buflen -= iaaddr_offset; | |
243 | *buf += iaaddr_offset; | |
244 | ||
245 | while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) { | |
246 | ||
247 | switch (opt) { | |
248 | case DHCP6_OPTION_IAADDR: | |
249 | ||
250 | addr = new0(DHCP6Address, 1); | |
251 | if (!addr) { | |
252 | r = -ENOMEM; | |
253 | goto error; | |
254 | } | |
255 | ||
256 | LIST_INIT(addresses, addr); | |
257 | ||
ee3a5027 | 258 | memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr)); |
c6affce8 | 259 | |
ee3a5027 PF |
260 | lt_valid = be32toh(addr->iaaddr.lifetime_valid); |
261 | lt_pref = be32toh(addr->iaaddr.lifetime_valid); | |
c6affce8 PF |
262 | |
263 | if (!lt_valid || lt_pref > lt_valid) { | |
264 | log_dhcp6_client(client, "IA preferred %ds > valid %ds", | |
265 | lt_pref, lt_valid); | |
266 | free(addr); | |
267 | } else { | |
268 | LIST_PREPEND(addresses, ia->addresses, addr); | |
269 | if (lt_valid < lt_min) | |
270 | lt_min = lt_valid; | |
271 | } | |
272 | ||
273 | break; | |
274 | ||
275 | case DHCP6_OPTION_STATUS_CODE: | |
276 | if (optlen < sizeof(status)) | |
277 | break; | |
278 | ||
279 | status = (*buf)[0] << 8 | (*buf)[1]; | |
280 | if (status) { | |
281 | log_dhcp6_client(client, "IA status %d", | |
282 | status); | |
283 | r = -EINVAL; | |
284 | goto error; | |
285 | } | |
286 | ||
287 | break; | |
288 | ||
289 | default: | |
290 | log_dhcp6_client(client, "Unknown IA option %d", opt); | |
291 | break; | |
292 | } | |
293 | ||
294 | *buflen -= optlen; | |
295 | *buf += optlen; | |
296 | } | |
297 | ||
298 | if (r == -ENOMSG) | |
299 | r = 0; | |
300 | ||
301 | if (!ia->lifetime_t1 && !ia->lifetime_t2) { | |
302 | lt_t1 = lt_min / 2; | |
303 | lt_t2 = lt_min / 10 * 8; | |
304 | ia->lifetime_t1 = htobe32(lt_t1); | |
305 | ia->lifetime_t2 = htobe32(lt_t2); | |
306 | ||
307 | log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero", | |
308 | lt_t1, lt_t2); | |
309 | } | |
310 | ||
311 | if (*buflen) | |
312 | r = -ENOMSG; | |
313 | ||
314 | error: | |
315 | *buf += *buflen; | |
316 | *buflen = 0; | |
317 | ||
318 | return r; | |
319 | } |