]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/dhcp6-option.c
networkd: make sure we remove udev fd from epoll *before* closing it
[thirdparty/systemd.git] / src / libsystemd-network / dhcp6-option.c
CommitLineData
f12ed3bf
PF
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
b553817c 6 Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
f12ed3bf
PF
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 28#include "util.h"
f96ccab7 29#include "strv.h"
f12ed3bf
PF
30
31#include "dhcp6-internal.h"
32#include "dhcp6-protocol.h"
f96ccab7 33#include "dns-domain.h"
f12ed3bf 34
f12ed3bf
PF
35#define DHCP6_OPTION_IA_NA_LEN 12
36#define DHCP6_OPTION_IA_TA_LEN 4
f12ed3bf 37
4903a73c
TG
38typedef struct DHCP6Option {
39 be16_t code;
40 be16_t len;
41 uint8_t data[];
c962cb68 42} _packed_ DHCP6Option;
4903a73c 43
f12ed3bf
PF
44static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
45 size_t optlen) {
c962cb68 46 DHCP6Option *option = (DHCP6Option*) *buf;
4903a73c 47
f12ed3bf
PF
48 assert_return(buf, -EINVAL);
49 assert_return(*buf, -EINVAL);
50 assert_return(buflen, -EINVAL);
51
4903a73c 52 if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option))
f12ed3bf
PF
53 return -ENOBUFS;
54
c962cb68
TG
55 option->code = htobe16(optcode);
56 option->len = htobe16(optlen);
f12ed3bf 57
4903a73c
TG
58 *buf += sizeof(DHCP6Option);
59 *buflen -= sizeof(DHCP6Option);
f12ed3bf
PF
60
61 return 0;
62}
63
64int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
65 size_t optlen, const void *optval) {
66 int r;
67
ed6ee219 68 assert_return(optval || optlen == 0, -EINVAL);
f12ed3bf
PF
69
70 r = option_append_hdr(buf, buflen, code, optlen);
71 if (r < 0)
72 return r;
73
ed6ee219
PF
74 if (optval)
75 memcpy(*buf, optval, optlen);
f12ed3bf
PF
76
77 *buf += optlen;
78 *buflen -= optlen;
79
80 return 0;
81}
82
83int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
84 uint16_t len;
85 uint8_t *ia_hdr;
86 size_t ia_buflen, ia_addrlen = 0;
87 DHCP6Address *addr;
88 int r;
89
90 assert_return(buf && *buf && buflen && ia, -EINVAL);
91
92 switch (ia->type) {
93 case DHCP6_OPTION_IA_NA:
94 len = DHCP6_OPTION_IA_NA_LEN;
95 break;
96
97 case DHCP6_OPTION_IA_TA:
98 len = DHCP6_OPTION_IA_TA_LEN;
99 break;
100
101 default:
102 return -EINVAL;
103 }
104
105 if (*buflen < len)
106 return -ENOBUFS;
107
108 ia_hdr = *buf;
109 ia_buflen = *buflen;
110
4903a73c
TG
111 *buf += sizeof(DHCP6Option);
112 *buflen -= sizeof(DHCP6Option);
f12ed3bf
PF
113
114 memcpy(*buf, &ia->id, len);
115
116 *buf += len;
117 *buflen -= len;
118
119 LIST_FOREACH(addresses, addr, ia->addresses) {
120 r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR,
ee3a5027 121 sizeof(addr->iaaddr));
f12ed3bf
PF
122 if (r < 0)
123 return r;
124
ee3a5027 125 memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
f12ed3bf 126
ee3a5027
PF
127 *buf += sizeof(addr->iaaddr);
128 *buflen -= sizeof(addr->iaaddr);
f12ed3bf 129
4903a73c 130 ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
f12ed3bf
PF
131 }
132
133 r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
134 if (r < 0)
135 return r;
136
137 return 0;
138}
139
c6affce8 140
c962cb68
TG
141static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
142 DHCP6Option *option = (DHCP6Option*) *buf;
c6affce8
PF
143 uint16_t len;
144
145 assert_return(buf, -EINVAL);
4903a73c 146 assert_return(optcode, -EINVAL);
c6affce8
PF
147 assert_return(optlen, -EINVAL);
148
4903a73c 149 if (*buflen < sizeof(DHCP6Option))
c6affce8
PF
150 return -ENOMSG;
151
c962cb68 152 len = be16toh(option->len);
c6affce8
PF
153
154 if (len > *buflen)
155 return -ENOMSG;
156
c962cb68 157 *optcode = be16toh(option->code);
c6affce8
PF
158 *optlen = len;
159
160 *buf += 4;
161 *buflen -= 4;
162
163 return 0;
164}
165
f12ed3bf
PF
166int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
167 size_t *optlen, uint8_t **optvalue) {
c6affce8 168 int r;
f12ed3bf 169
c6affce8 170 assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
f12ed3bf 171
c6affce8
PF
172 r = option_parse_hdr(buf, buflen, optcode, optlen);
173 if (r < 0)
174 return r;
f12ed3bf 175
c6affce8 176 if (*optlen > *buflen)
f12ed3bf
PF
177 return -ENOBUFS;
178
c6affce8
PF
179 *optvalue = *buf;
180 *buflen -= *optlen;
181 *buf += *optlen;
f12ed3bf
PF
182
183 return 0;
184}
c6affce8
PF
185
186int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
187 DHCP6IA *ia) {
188 int r;
189 uint16_t opt, status;
190 size_t optlen;
191 size_t iaaddr_offset;
192 DHCP6Address *addr;
193 uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
194
195 assert_return(ia, -EINVAL);
196 assert_return(!ia->addresses, -EINVAL);
197
198 switch (iatype) {
199 case DHCP6_OPTION_IA_NA:
200
4903a73c 201 if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) +
ee3a5027 202 sizeof(addr->iaaddr)) {
c6affce8
PF
203 r = -ENOBUFS;
204 goto error;
205 }
206
207 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
208 memcpy(&ia->id, *buf, iaaddr_offset);
209
210 lt_t1 = be32toh(ia->lifetime_t1);
211 lt_t2 = be32toh(ia->lifetime_t2);
212
213 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
214 log_dhcp6_client(client, "IA T1 %ds > T2 %ds",
215 lt_t1, lt_t2);
216 r = -EINVAL;
217 goto error;
218 }
219
220 break;
221
222 case DHCP6_OPTION_IA_TA:
4903a73c 223 if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) +
ee3a5027 224 sizeof(addr->iaaddr)) {
c6affce8
PF
225 r = -ENOBUFS;
226 goto error;
227 }
228
229 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
230 memcpy(&ia->id, *buf, iaaddr_offset);
231
232 ia->lifetime_t1 = 0;
233 ia->lifetime_t2 = 0;
234
235 break;
236
237 default:
238 r = -ENOMSG;
239 goto error;
240 }
241
242 ia->type = iatype;
243
244 *buflen -= iaaddr_offset;
245 *buf += iaaddr_offset;
246
247 while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
248
249 switch (opt) {
250 case DHCP6_OPTION_IAADDR:
251
252 addr = new0(DHCP6Address, 1);
253 if (!addr) {
254 r = -ENOMEM;
255 goto error;
256 }
257
258 LIST_INIT(addresses, addr);
259
ee3a5027 260 memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr));
c6affce8 261
ee3a5027
PF
262 lt_valid = be32toh(addr->iaaddr.lifetime_valid);
263 lt_pref = be32toh(addr->iaaddr.lifetime_valid);
c6affce8
PF
264
265 if (!lt_valid || lt_pref > lt_valid) {
266 log_dhcp6_client(client, "IA preferred %ds > valid %ds",
267 lt_pref, lt_valid);
268 free(addr);
269 } else {
270 LIST_PREPEND(addresses, ia->addresses, addr);
271 if (lt_valid < lt_min)
272 lt_min = lt_valid;
273 }
274
275 break;
276
277 case DHCP6_OPTION_STATUS_CODE:
278 if (optlen < sizeof(status))
279 break;
280
281 status = (*buf)[0] << 8 | (*buf)[1];
282 if (status) {
283 log_dhcp6_client(client, "IA status %d",
284 status);
285 r = -EINVAL;
286 goto error;
287 }
288
289 break;
290
291 default:
292 log_dhcp6_client(client, "Unknown IA option %d", opt);
293 break;
294 }
295
296 *buflen -= optlen;
297 *buf += optlen;
298 }
299
300 if (r == -ENOMSG)
301 r = 0;
302
303 if (!ia->lifetime_t1 && !ia->lifetime_t2) {
304 lt_t1 = lt_min / 2;
305 lt_t2 = lt_min / 10 * 8;
306 ia->lifetime_t1 = htobe32(lt_t1);
307 ia->lifetime_t2 = htobe32(lt_t2);
308
309 log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero",
310 lt_t1, lt_t2);
311 }
312
313 if (*buflen)
314 r = -ENOMSG;
315
316error:
317 *buf += *buflen;
318 *buflen = 0;
319
320 return r;
321}
b553817c
PF
322
323int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
324 struct in6_addr **addrs, size_t count,
325 size_t *allocated) {
326
327 if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
328 return -EINVAL;
329
330 if (!GREEDY_REALLOC(*addrs, *allocated,
331 count * sizeof(struct in6_addr) + optlen))
332 return -ENOMEM;
333
334 memcpy(*addrs + count, optval, optlen);
335
336 count += optlen / sizeof(struct in6_addr);
337
338 return count;
339}
f96ccab7
PF
340
341int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen,
342 char ***str_arr)
343{
344 size_t pos = 0, idx = 0;
345 _cleanup_free_ char **names = NULL;
346 int r;
347
348 assert_return(optlen > 1, -ENODATA);
349 assert_return(optval[optlen] == '\0', -EINVAL);
350
351 while (pos < optlen) {
352 _cleanup_free_ char *ret = NULL;
353 size_t n = 0, allocated = 0;
354 bool first = true;
355
356 for (;;) {
357 uint8_t c;
358
359 c = optval[pos++];
360
361 if (c == 0)
362 /* End of name */
363 break;
364 else if (c <= 63) {
365 _cleanup_free_ char *t = NULL;
366 const char *label;
367
368 /* Literal label */
369 label = (const char *)&optval[pos];
370 pos += c;
371 if (pos > optlen)
372 return -EMSGSIZE;
373
374 r = dns_label_escape(label, c, &t);
375 if (r < 0)
376 goto fail;
377
378 if (!GREEDY_REALLOC0(ret, allocated, n + !first + strlen(t) + 1)) {
379 r = -ENOMEM;
380 goto fail;
381 }
382
383 if (!first)
384 ret[n++] = '.';
385 else
386 first = false;
387
388 memcpy(ret + n, t, r);
389 n += r;
390 continue;
391 } else {
392 r = -EBADMSG;
393 goto fail;
394 }
395 }
396
397 if (!GREEDY_REALLOC(ret, allocated, n + 1)) {
398 r = -ENOMEM;
399 goto fail;
400 }
401
402 ret[n] = 0;
403
404 r = strv_extend(&names, ret);
405 if (r < 0)
406 goto fail;
407
f96ccab7
PF
408 idx++;
409 }
410
411 *str_arr = names;
412 names = NULL;
413
414 return idx;
415
416fail:
417 return r;
418}