]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/dhcp6-option.c
resolved: actually, the peer with the lower IP address wins conflicts
[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"
27#include "util.h"
28
29#include "dhcp6-internal.h"
30#include "dhcp6-protocol.h"
31
32#define DHCP6_OPTION_HDR_LEN 4
33#define DHCP6_OPTION_IA_NA_LEN 12
34#define DHCP6_OPTION_IA_TA_LEN 4
35#define DHCP6_OPTION_IAADDR_LEN 24
36
37static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
38 size_t optlen) {
39 assert_return(buf, -EINVAL);
40 assert_return(*buf, -EINVAL);
41 assert_return(buflen, -EINVAL);
42
43 if (optlen > 0xffff || *buflen < optlen + DHCP6_OPTION_HDR_LEN)
44 return -ENOBUFS;
45
46 (*buf)[0] = optcode >> 8;
47 (*buf)[1] = optcode & 0xff;
48 (*buf)[2] = optlen >> 8;
49 (*buf)[3] = optlen & 0xff;
50
51 *buf += DHCP6_OPTION_HDR_LEN;
52 *buflen -= DHCP6_OPTION_HDR_LEN;
53
54 return 0;
55}
56
57int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
58 size_t optlen, const void *optval) {
59 int r;
60
ed6ee219 61 assert_return(optval || optlen == 0, -EINVAL);
f12ed3bf
PF
62
63 r = option_append_hdr(buf, buflen, code, optlen);
64 if (r < 0)
65 return r;
66
ed6ee219
PF
67 if (optval)
68 memcpy(*buf, optval, optlen);
f12ed3bf
PF
69
70 *buf += optlen;
71 *buflen -= optlen;
72
73 return 0;
74}
75
76int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
77 uint16_t len;
78 uint8_t *ia_hdr;
79 size_t ia_buflen, ia_addrlen = 0;
80 DHCP6Address *addr;
81 int r;
82
83 assert_return(buf && *buf && buflen && ia, -EINVAL);
84
85 switch (ia->type) {
86 case DHCP6_OPTION_IA_NA:
87 len = DHCP6_OPTION_IA_NA_LEN;
88 break;
89
90 case DHCP6_OPTION_IA_TA:
91 len = DHCP6_OPTION_IA_TA_LEN;
92 break;
93
94 default:
95 return -EINVAL;
96 }
97
98 if (*buflen < len)
99 return -ENOBUFS;
100
101 ia_hdr = *buf;
102 ia_buflen = *buflen;
103
104 *buf += DHCP6_OPTION_HDR_LEN;
105 *buflen -= DHCP6_OPTION_HDR_LEN;
106
107 memcpy(*buf, &ia->id, len);
108
109 *buf += len;
110 *buflen -= len;
111
112 LIST_FOREACH(addresses, addr, ia->addresses) {
113 r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR,
114 DHCP6_OPTION_IAADDR_LEN);
115 if (r < 0)
116 return r;
117
118 memcpy(*buf, &addr->address, DHCP6_OPTION_IAADDR_LEN);
119
120 *buf += DHCP6_OPTION_IAADDR_LEN;
121 *buflen -= DHCP6_OPTION_IAADDR_LEN;
122
123 ia_addrlen += DHCP6_OPTION_HDR_LEN + DHCP6_OPTION_IAADDR_LEN;
124 }
125
126 r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
127 if (r < 0)
128 return r;
129
130 return 0;
131}
132
c6affce8
PF
133
134static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *opt,
135 size_t *optlen) {
136 uint16_t len;
137
138 assert_return(buf, -EINVAL);
139 assert_return(opt, -EINVAL);
140 assert_return(optlen, -EINVAL);
141
142 if (*buflen < 4)
143 return -ENOMSG;
144
145 len = (*buf)[2] << 8 | (*buf)[3];
146
147 if (len > *buflen)
148 return -ENOMSG;
149
150 *opt = (*buf)[0] << 8 | (*buf)[1];
151 *optlen = len;
152
153 *buf += 4;
154 *buflen -= 4;
155
156 return 0;
157}
158
f12ed3bf
PF
159int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
160 size_t *optlen, uint8_t **optvalue) {
c6affce8 161 int r;
f12ed3bf 162
c6affce8 163 assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
f12ed3bf 164
c6affce8
PF
165 r = option_parse_hdr(buf, buflen, optcode, optlen);
166 if (r < 0)
167 return r;
f12ed3bf 168
c6affce8 169 if (*optlen > *buflen)
f12ed3bf
PF
170 return -ENOBUFS;
171
c6affce8
PF
172 *optvalue = *buf;
173 *buflen -= *optlen;
174 *buf += *optlen;
f12ed3bf
PF
175
176 return 0;
177}
c6affce8
PF
178
179int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
180 DHCP6IA *ia) {
181 int r;
182 uint16_t opt, status;
183 size_t optlen;
184 size_t iaaddr_offset;
185 DHCP6Address *addr;
186 uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
187
188 assert_return(ia, -EINVAL);
189 assert_return(!ia->addresses, -EINVAL);
190
191 switch (iatype) {
192 case DHCP6_OPTION_IA_NA:
193
194 if (*buflen < DHCP6_OPTION_IA_NA_LEN + DHCP6_OPTION_HDR_LEN +
195 DHCP6_OPTION_IAADDR_LEN) {
196 r = -ENOBUFS;
197 goto error;
198 }
199
200 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
201 memcpy(&ia->id, *buf, iaaddr_offset);
202
203 lt_t1 = be32toh(ia->lifetime_t1);
204 lt_t2 = be32toh(ia->lifetime_t2);
205
206 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
207 log_dhcp6_client(client, "IA T1 %ds > T2 %ds",
208 lt_t1, lt_t2);
209 r = -EINVAL;
210 goto error;
211 }
212
213 break;
214
215 case DHCP6_OPTION_IA_TA:
216 if (*buflen < DHCP6_OPTION_IA_TA_LEN + DHCP6_OPTION_HDR_LEN +
217 DHCP6_OPTION_IAADDR_LEN) {
218 r = -ENOBUFS;
219 goto error;
220 }
221
222 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
223 memcpy(&ia->id, *buf, iaaddr_offset);
224
225 ia->lifetime_t1 = 0;
226 ia->lifetime_t2 = 0;
227
228 break;
229
230 default:
231 r = -ENOMSG;
232 goto error;
233 }
234
235 ia->type = iatype;
236
237 *buflen -= iaaddr_offset;
238 *buf += iaaddr_offset;
239
240 while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
241
242 switch (opt) {
243 case DHCP6_OPTION_IAADDR:
244
245 addr = new0(DHCP6Address, 1);
246 if (!addr) {
247 r = -ENOMEM;
248 goto error;
249 }
250
251 LIST_INIT(addresses, addr);
252
253 memcpy(&addr->address, *buf, DHCP6_OPTION_IAADDR_LEN);
254
255 lt_valid = be32toh(addr->lifetime_valid);
256 lt_pref = be32toh(addr->lifetime_valid);
257
258 if (!lt_valid || lt_pref > lt_valid) {
259 log_dhcp6_client(client, "IA preferred %ds > valid %ds",
260 lt_pref, lt_valid);
261 free(addr);
262 } else {
263 LIST_PREPEND(addresses, ia->addresses, addr);
264 if (lt_valid < lt_min)
265 lt_min = lt_valid;
266 }
267
268 break;
269
270 case DHCP6_OPTION_STATUS_CODE:
271 if (optlen < sizeof(status))
272 break;
273
274 status = (*buf)[0] << 8 | (*buf)[1];
275 if (status) {
276 log_dhcp6_client(client, "IA status %d",
277 status);
278 r = -EINVAL;
279 goto error;
280 }
281
282 break;
283
284 default:
285 log_dhcp6_client(client, "Unknown IA option %d", opt);
286 break;
287 }
288
289 *buflen -= optlen;
290 *buf += optlen;
291 }
292
293 if (r == -ENOMSG)
294 r = 0;
295
296 if (!ia->lifetime_t1 && !ia->lifetime_t2) {
297 lt_t1 = lt_min / 2;
298 lt_t2 = lt_min / 10 * 8;
299 ia->lifetime_t1 = htobe32(lt_t1);
300 ia->lifetime_t2 = htobe32(lt_t2);
301
302 log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero",
303 lt_t1, lt_t2);
304 }
305
306 if (*buflen)
307 r = -ENOMSG;
308
309error:
310 *buf += *buflen;
311 *buflen = 0;
312
313 return r;
314}