]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/dhcp6-option.c
sd-dhcp6-client: Save a DHCPv6 lease also with Information Reply
[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
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
36typedef struct DHCP6Option {
37 be16_t code;
38 be16_t len;
39 uint8_t data[];
c962cb68 40} _packed_ DHCP6Option;
4903a73c 41
f12ed3bf
PF
42static 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
62int 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
81int 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
139static 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
164int 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
184int 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
314error:
315 *buf += *buflen;
316 *buflen = 0;
317
318 return r;
319}