]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-dhcp6-lease.c
sd-dhcp6-client: logs invalid NTP option
[thirdparty/systemd.git] / src / libsystemd-network / sd-dhcp6-lease.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /***
3 Copyright © 2014-2015 Intel Corporation. All rights reserved.
4 ***/
5
6 #include <errno.h>
7
8 #include "alloc-util.h"
9 #include "dhcp6-internal.h"
10 #include "dhcp6-lease-internal.h"
11 #include "strv.h"
12
13 #define IRT_DEFAULT (1 * USEC_PER_DAY)
14 #define IRT_MINIMUM (600 * USEC_PER_SEC)
15
16 static void dhcp6_lease_set_timestamp(sd_dhcp6_lease *lease, const triple_timestamp *timestamp) {
17 assert(lease);
18
19 if (timestamp && triple_timestamp_is_set(timestamp))
20 lease->timestamp = *timestamp;
21 else
22 triple_timestamp_get(&lease->timestamp);
23 }
24
25 int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret) {
26 assert_return(lease, -EINVAL);
27 assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
28 assert_return(clock_supported(clock), -EOPNOTSUPP);
29 assert_return(ret, -EINVAL);
30
31 if (!triple_timestamp_is_set(&lease->timestamp))
32 return -ENODATA;
33
34 *ret = triple_timestamp_by_clock(&lease->timestamp, clock);
35 return 0;
36 }
37
38 static usec_t sec2usec(uint32_t sec) {
39 return sec == UINT32_MAX ? USEC_INFINITY : sec * USEC_PER_SEC;
40 }
41
42 static void dhcp6_lease_set_lifetime(sd_dhcp6_lease *lease) {
43 uint32_t t1 = UINT32_MAX, t2 = UINT32_MAX, min_valid_lt = UINT32_MAX;
44 DHCP6Address *a;
45
46 assert(lease);
47 assert(lease->ia_na || lease->ia_pd);
48
49 if (lease->ia_na) {
50 t1 = MIN(t1, be32toh(lease->ia_na->header.lifetime_t1));
51 t2 = MIN(t2, be32toh(lease->ia_na->header.lifetime_t2));
52
53 LIST_FOREACH(addresses, a, lease->ia_na->addresses)
54 min_valid_lt = MIN(min_valid_lt, be32toh(a->iaaddr.lifetime_valid));
55 }
56
57 if (lease->ia_pd) {
58 t1 = MIN(t1, be32toh(lease->ia_pd->header.lifetime_t1));
59 t2 = MIN(t2, be32toh(lease->ia_pd->header.lifetime_t2));
60
61 LIST_FOREACH(addresses, a, lease->ia_pd->addresses)
62 min_valid_lt = MIN(min_valid_lt, be32toh(a->iapdprefix.lifetime_valid));
63 }
64
65 if (t2 == 0 || t2 > min_valid_lt) {
66 /* If T2 is zero or longer than the minimum valid lifetime of the addresses or prefixes,
67 * then adjust lifetime with it. */
68 t1 = min_valid_lt / 2;
69 t2 = min_valid_lt / 10 * 8;
70 }
71
72 lease->lifetime_valid = sec2usec(min_valid_lt);
73 lease->lifetime_t1 = sec2usec(t1);
74 lease->lifetime_t2 = sec2usec(t2);
75 }
76
77 int dhcp6_lease_get_lifetime(sd_dhcp6_lease *lease, usec_t *ret_t1, usec_t *ret_t2, usec_t *ret_valid) {
78 assert(lease);
79
80 if (!lease->ia_na && !lease->ia_pd)
81 return -ENODATA;
82
83 if (ret_t1)
84 *ret_t1 = lease->lifetime_t1;
85 if (ret_t2)
86 *ret_t2 = lease->lifetime_t2;
87 if (ret_valid)
88 *ret_valid = lease->lifetime_valid;
89 return 0;
90 }
91
92 static void dhcp6_lease_set_server_address(sd_dhcp6_lease *lease, const struct in6_addr *server_address) {
93 assert(lease);
94
95 if (server_address)
96 lease->server_address = *server_address;
97 else
98 lease->server_address = (struct in6_addr) {};
99 }
100
101 int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *ret) {
102 assert_return(lease, -EINVAL);
103 assert_return(ret, -EINVAL);
104
105 *ret = lease->server_address;
106 return 0;
107 }
108
109 void dhcp6_ia_clear_addresses(DHCP6IA *ia) {
110 DHCP6Address *a, *n;
111
112 assert(ia);
113
114 LIST_FOREACH_SAFE(addresses, a, n, ia->addresses)
115 free(a);
116
117 ia->addresses = NULL;
118 }
119
120 DHCP6IA *dhcp6_ia_free(DHCP6IA *ia) {
121 if (!ia)
122 return NULL;
123
124 dhcp6_ia_clear_addresses(ia);
125
126 return mfree(ia);
127 }
128
129 int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) {
130 uint8_t *clientid = NULL;
131
132 assert(lease);
133 assert(id || len == 0);
134
135 if (len > 0) {
136 clientid = memdup(id, len);
137 if (!clientid)
138 return -ENOMEM;
139 }
140
141 free_and_replace(lease->clientid, clientid);
142 lease->clientid_len = len;
143
144 return 0;
145 }
146
147 int dhcp6_lease_get_clientid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) {
148 assert(lease);
149
150 if (!lease->clientid)
151 return -ENODATA;
152
153 if (ret_id)
154 *ret_id = lease->clientid;
155 if (ret_len)
156 *ret_len = lease->clientid_len;
157
158 return 0;
159 }
160
161 int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len) {
162 uint8_t *serverid = NULL;
163
164 assert(lease);
165 assert(id || len == 0);
166
167 if (len > 0) {
168 serverid = memdup(id, len);
169 if (!serverid)
170 return -ENOMEM;
171 }
172
173 free_and_replace(lease->serverid, serverid);
174 lease->serverid_len = len;
175
176 return 0;
177 }
178
179 int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **ret_id, size_t *ret_len) {
180 assert(lease);
181
182 if (!lease->serverid)
183 return -ENODATA;
184
185 if (ret_id)
186 *ret_id = lease->serverid;
187 if (ret_len)
188 *ret_len = lease->serverid_len;
189 return 0;
190 }
191
192 int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) {
193 assert(lease);
194
195 lease->preference = preference;
196 return 0;
197 }
198
199 int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *ret) {
200 assert(lease);
201 assert(ret);
202
203 *ret = lease->preference;
204 return 0;
205 }
206
207 int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) {
208 assert(lease);
209
210 lease->rapid_commit = true;
211 return 0;
212 }
213
214 int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *ret) {
215 assert(lease);
216 assert(ret);
217
218 *ret = lease->rapid_commit;
219 return 0;
220 }
221
222 int sd_dhcp6_lease_get_address(
223 sd_dhcp6_lease *lease,
224 struct in6_addr *ret_addr,
225 uint32_t *ret_lifetime_preferred,
226 uint32_t *ret_lifetime_valid) {
227
228 assert_return(lease, -EINVAL);
229
230 if (!lease->addr_iter)
231 return -ENODATA;
232
233 if (ret_addr)
234 *ret_addr = lease->addr_iter->iaaddr.address;
235 if (ret_lifetime_preferred)
236 *ret_lifetime_preferred = be32toh(lease->addr_iter->iaaddr.lifetime_preferred);
237 if (ret_lifetime_valid)
238 *ret_lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid);
239
240 lease->addr_iter = lease->addr_iter->addresses_next;
241 return 0;
242 }
243
244 void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) {
245 if (lease)
246 lease->addr_iter = lease->ia_na ? lease->ia_na->addresses : NULL;
247 }
248
249 int sd_dhcp6_lease_get_pd(
250 sd_dhcp6_lease *lease,
251 struct in6_addr *ret_prefix,
252 uint8_t *ret_prefix_len,
253 uint32_t *ret_lifetime_preferred,
254 uint32_t *ret_lifetime_valid) {
255
256 assert_return(lease, -EINVAL);
257
258 if (!lease->prefix_iter)
259 return -ENODATA;
260
261 if (ret_prefix)
262 *ret_prefix = lease->prefix_iter->iapdprefix.address;
263 if (ret_prefix_len)
264 *ret_prefix_len = lease->prefix_iter->iapdprefix.prefixlen;
265 if (ret_lifetime_preferred)
266 *ret_lifetime_preferred = be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred);
267 if (ret_lifetime_valid)
268 *ret_lifetime_valid = be32toh(lease->prefix_iter->iapdprefix.lifetime_valid);
269
270 lease->prefix_iter = lease->prefix_iter->addresses_next;
271 return 0;
272 }
273
274 void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) {
275 if (lease)
276 lease->prefix_iter = lease->ia_pd ? lease->ia_pd->addresses : NULL;
277 }
278
279 int dhcp6_lease_add_dns(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
280 assert(lease);
281 assert(optval || optlen == 0);
282
283 if (optlen == 0)
284 return 0;
285
286 return dhcp6_option_parse_addresses(optval, optlen, &lease->dns, &lease->dns_count);
287 }
288
289 int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret) {
290 assert_return(lease, -EINVAL);
291
292 if (!lease->dns)
293 return -ENODATA;
294
295 if (ret)
296 *ret = lease->dns;
297
298 return lease->dns_count;
299 }
300
301 int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
302 _cleanup_strv_free_ char **domains = NULL;
303 int r;
304
305 assert(lease);
306 assert(optval || optlen == 0);
307
308 if (optlen == 0)
309 return 0;
310
311 r = dhcp6_option_parse_domainname_list(optval, optlen, &domains);
312 if (r < 0)
313 return r;
314
315 return strv_extend_strv(&lease->domains, domains, true);
316 }
317
318 int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) {
319 assert_return(lease, -EINVAL);
320 assert_return(ret, -EINVAL);
321
322 if (!lease->domains)
323 return -ENODATA;
324
325 *ret = lease->domains;
326 return strv_length(lease->domains);
327 }
328
329 int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
330 int r;
331
332 assert(lease);
333 assert(optval || optlen == 0);
334
335 for (size_t offset = 0; offset < optlen;) {
336 const uint8_t *subval;
337 size_t sublen;
338 uint16_t subopt;
339
340 r = dhcp6_option_parse(optval, optlen, &offset, &subopt, &sublen, &subval);
341 if (r < 0)
342 return r;
343
344 switch(subopt) {
345 case DHCP6_NTP_SUBOPTION_SRV_ADDR:
346 case DHCP6_NTP_SUBOPTION_MC_ADDR:
347 if (sublen != 16)
348 return -EINVAL;
349
350 r = dhcp6_option_parse_addresses(subval, sublen, &lease->ntp, &lease->ntp_count);
351 if (r < 0)
352 return r;
353
354 break;
355
356 case DHCP6_NTP_SUBOPTION_SRV_FQDN: {
357 _cleanup_free_ char *server = NULL;
358
359 r = dhcp6_option_parse_domainname(subval, sublen, &server);
360 if (r < 0)
361 return r;
362
363 if (strv_contains(lease->ntp_fqdn, server))
364 continue;
365
366 r = strv_consume(&lease->ntp_fqdn, TAKE_PTR(server));
367 if (r < 0)
368 return r;
369
370 break;
371 }}
372 }
373
374 return 0;
375 }
376
377 int dhcp6_lease_add_sntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
378 assert(lease);
379 assert(optval || optlen == 0);
380
381 if (optlen == 0)
382 return 0;
383
384 /* SNTP option is defined in RFC4075, and deprecated by RFC5908. */
385 return dhcp6_option_parse_addresses(optval, optlen, &lease->sntp, &lease->sntp_count);
386 }
387
388 int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret) {
389 assert_return(lease, -EINVAL);
390
391 if (lease->ntp) {
392 if (ret)
393 *ret = lease->ntp;
394 return lease->ntp_count;
395 }
396
397 if (lease->sntp && !lease->ntp_fqdn) {
398 /* Fallback to the deprecated SNTP option. */
399 if (ret)
400 *ret = lease->sntp;
401 return lease->sntp_count;
402 }
403
404 return -ENODATA;
405 }
406
407 int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret) {
408 assert_return(lease, -EINVAL);
409
410 if (!lease->ntp_fqdn)
411 return -ENODATA;
412
413 if (ret)
414 *ret = lease->ntp_fqdn;
415 return strv_length(lease->ntp_fqdn);
416 }
417
418 int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
419 char *fqdn;
420 int r;
421
422 assert(lease);
423 assert(optval || optlen == 0);
424
425 if (optlen == 0)
426 return 0;
427
428 if (optlen < 2)
429 return -ENODATA;
430
431 /* Ignore the flags field, it doesn't carry any useful
432 information for clients. */
433 r = dhcp6_option_parse_domainname(optval + 1, optlen - 1, &fqdn);
434 if (r < 0)
435 return r;
436
437 return free_and_replace(lease->fqdn, fqdn);
438 }
439
440 int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret) {
441 assert_return(lease, -EINVAL);
442 assert_return(ret, -EINVAL);
443
444 if (!lease->fqdn)
445 return -ENODATA;
446
447 *ret = lease->fqdn;
448 return 0;
449 }
450
451 static int dhcp6_lease_parse_message(
452 sd_dhcp6_client *client,
453 sd_dhcp6_lease *lease,
454 const DHCP6Message *message,
455 size_t len) {
456
457 usec_t irt = IRT_DEFAULT;
458 int r;
459
460 assert(client);
461 assert(lease);
462 assert(message);
463 assert(len >= sizeof(DHCP6Message));
464
465 len -= sizeof(DHCP6Message);
466 for (size_t offset = 0; offset < len;) {
467 uint16_t optcode;
468 size_t optlen;
469 const uint8_t *optval;
470
471 r = dhcp6_option_parse(message->options, len, &offset, &optcode, &optlen, &optval);
472 if (r < 0)
473 return r;
474
475 switch (optcode) {
476 case SD_DHCP6_OPTION_CLIENTID:
477 if (dhcp6_lease_get_clientid(lease, NULL, NULL) >= 0)
478 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple client IDs",
479 dhcp6_message_type_to_string(message->type));
480
481 r = dhcp6_lease_set_clientid(lease, optval, optlen);
482 if (r < 0)
483 return r;
484
485 break;
486
487 case SD_DHCP6_OPTION_SERVERID:
488 if (dhcp6_lease_get_serverid(lease, NULL, NULL) >= 0)
489 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple server IDs",
490 dhcp6_message_type_to_string(message->type));
491
492 r = dhcp6_lease_set_serverid(lease, optval, optlen);
493 if (r < 0)
494 return r;
495
496 break;
497
498 case SD_DHCP6_OPTION_PREFERENCE:
499 if (optlen != 1)
500 return -EINVAL;
501
502 r = dhcp6_lease_set_preference(lease, optval[0]);
503 if (r < 0)
504 return r;
505
506 break;
507
508 case SD_DHCP6_OPTION_STATUS_CODE: {
509 _cleanup_free_ char *msg = NULL;
510
511 r = dhcp6_option_parse_status(optval, optlen, &msg);
512 if (r < 0)
513 return r;
514
515 if (r > 0)
516 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
517 "Received %s message with non-zero status: %s%s%s",
518 dhcp6_message_type_to_string(message->type),
519 strempty(msg), isempty(msg) ? "" : ": ",
520 dhcp6_message_status_to_string(r));
521 break;
522 }
523 case SD_DHCP6_OPTION_IA_NA: {
524 _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
525
526 if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
527 log_dhcp6_client(client, "Ignoring IA NA option in information requesting mode.");
528 break;
529 }
530
531 r = dhcp6_option_parse_ia(client, client->ia_na.header.id, optcode, optlen, optval, &ia);
532 if (r == -ENOMEM)
533 return r;
534 if (r < 0)
535 continue;
536
537 if (lease->ia_na) {
538 log_dhcp6_client(client, "Received duplicate matching IA_NA option, ignoring.");
539 continue;
540 }
541
542 dhcp6_ia_free(lease->ia_na);
543 lease->ia_na = TAKE_PTR(ia);
544 break;
545 }
546 case SD_DHCP6_OPTION_IA_PD: {
547 _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
548
549 if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
550 log_dhcp6_client(client, "Ignoring IA PD option in information requesting mode.");
551 break;
552 }
553
554 r = dhcp6_option_parse_ia(client, client->ia_pd.header.id, optcode, optlen, optval, &ia);
555 if (r == -ENOMEM)
556 return r;
557 if (r < 0)
558 continue;
559
560 if (lease->ia_pd) {
561 log_dhcp6_client(client, "Received duplicate matching IA_PD option, ignoring.");
562 continue;
563 }
564
565 dhcp6_ia_free(lease->ia_pd);
566 lease->ia_pd = TAKE_PTR(ia);
567 break;
568 }
569 case SD_DHCP6_OPTION_RAPID_COMMIT:
570 r = dhcp6_lease_set_rapid_commit(lease);
571 if (r < 0)
572 return r;
573
574 break;
575
576 case SD_DHCP6_OPTION_DNS_SERVERS:
577 r = dhcp6_lease_add_dns(lease, optval, optlen);
578 if (r < 0)
579 log_dhcp6_client_errno(client, r, "Failed to parse DNS server option, ignoring: %m");
580
581 break;
582
583 case SD_DHCP6_OPTION_DOMAIN_LIST:
584 r = dhcp6_lease_add_domains(lease, optval, optlen);
585 if (r < 0)
586 log_dhcp6_client_errno(client, r, "Failed to parse domain list option, ignoring: %m");
587
588 break;
589
590 case SD_DHCP6_OPTION_NTP_SERVER:
591 r = dhcp6_lease_add_ntp(lease, optval, optlen);
592 if (r < 0)
593 log_dhcp6_client_errno(client, r, "Failed to parse NTP server option, ignoring: %m");
594
595 break;
596
597 case SD_DHCP6_OPTION_SNTP_SERVERS:
598 r = dhcp6_lease_add_sntp(lease, optval, optlen);
599 if (r < 0)
600 log_dhcp6_client_errno(client, r, "Failed to parse SNTP server option, ignoring: %m");
601
602 break;
603
604 case SD_DHCP6_OPTION_CLIENT_FQDN:
605 r = dhcp6_lease_set_fqdn(lease, optval, optlen);
606 if (r < 0)
607 log_dhcp6_client_errno(client, r, "Failed to parse FQDN option, ignoring: %m");
608
609 break;
610
611 case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
612 if (optlen != 4)
613 return -EINVAL;
614
615 irt = unaligned_read_be32((be32_t *) optval) * USEC_PER_SEC;
616 break;
617 }
618 }
619
620 uint8_t *clientid;
621 size_t clientid_len;
622 if (dhcp6_lease_get_clientid(lease, &clientid, &clientid_len) < 0)
623 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
624 "%s message does not contain client ID. Ignoring.",
625 dhcp6_message_type_to_string(message->type));
626
627 if (memcmp_nn(clientid, clientid_len, &client->duid, client->duid_len) != 0)
628 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
629 "The client ID in %s message does not match. Ignoring.",
630 dhcp6_message_type_to_string(message->type));
631
632 if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
633 client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM);
634 log_dhcp6_client(client, "New information request will be refused in %s.",
635 FORMAT_TIMESPAN(client->information_refresh_time_usec, USEC_PER_SEC));
636
637 } else {
638 r = dhcp6_lease_get_serverid(lease, NULL, NULL);
639 if (r < 0)
640 return log_dhcp6_client_errno(client, r, "%s has no server id",
641 dhcp6_message_type_to_string(message->type));
642
643 if (!lease->ia_na && !lease->ia_pd)
644 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
645 "No IA_PD prefix or IA_NA address received. Ignoring.");
646
647 dhcp6_lease_set_lifetime(lease);
648 }
649
650 return 0;
651 }
652
653 static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
654 if (!lease)
655 return NULL;
656
657 free(lease->clientid);
658 free(lease->serverid);
659 dhcp6_ia_free(lease->ia_na);
660 dhcp6_ia_free(lease->ia_pd);
661 free(lease->dns);
662 free(lease->fqdn);
663 strv_free(lease->domains);
664 free(lease->ntp);
665 strv_free(lease->ntp_fqdn);
666 free(lease->sntp);
667
668 return mfree(lease);
669 }
670
671 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_lease, sd_dhcp6_lease, dhcp6_lease_free);
672
673 int dhcp6_lease_new(sd_dhcp6_lease **ret) {
674 sd_dhcp6_lease *lease;
675
676 assert(ret);
677
678 lease = new(sd_dhcp6_lease, 1);
679 if (!lease)
680 return -ENOMEM;
681
682 *lease = (sd_dhcp6_lease) {
683 .n_ref = 1,
684 };
685
686 *ret = lease;
687 return 0;
688 }
689
690 int dhcp6_lease_new_from_message(
691 sd_dhcp6_client *client,
692 const DHCP6Message *message,
693 size_t len,
694 const triple_timestamp *timestamp,
695 const struct in6_addr *server_address,
696 sd_dhcp6_lease **ret) {
697
698 _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
699 int r;
700
701 assert(client);
702 assert(message);
703 assert(len >= sizeof(DHCP6Message));
704 assert(ret);
705
706 r = dhcp6_lease_new(&lease);
707 if (r < 0)
708 return r;
709
710 dhcp6_lease_set_timestamp(lease, timestamp);
711 dhcp6_lease_set_server_address(lease, server_address);
712
713 r = dhcp6_lease_parse_message(client, lease, message, len);
714 if (r < 0)
715 return r;
716
717 *ret = TAKE_PTR(lease);
718 return 0;
719 }