chiark / gitweb /
sd-bus: rename "connection name" to "description" for the sd-bus API too
[elogind.git] / src / libsystemd-network / dhcp6-option.c
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 "unaligned.h"
28 #include "util.h"
29
30 #include "dhcp6-internal.h"
31 #include "dhcp6-protocol.h"
32
33 #define DHCP6_OPTION_IA_NA_LEN                  12
34 #define DHCP6_OPTION_IA_TA_LEN                  4
35
36 typedef struct DHCP6Option {
37         be16_t code;
38         be16_t len;
39         uint8_t data[];
40 } DHCP6Option;
41
42 static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
43                              size_t optlen) {
44         DHCP6Option *option = (DHCP6Option*) *buf; /* unaligned! */
45
46         assert_return(buf, -EINVAL);
47         assert_return(*buf, -EINVAL);
48         assert_return(buflen, -EINVAL);
49
50         if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option))
51                 return -ENOBUFS;
52
53         unaligned_write_be16(&option->code, optcode);
54         unaligned_write_be16(&option->len, (uint16_t) optlen);
55
56         *buf += sizeof(DHCP6Option);
57         *buflen -= sizeof(DHCP6Option);
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
66         assert_return(optval || optlen == 0, -EINVAL);
67
68         r = option_append_hdr(buf, buflen, code, optlen);
69         if (r < 0)
70                 return r;
71
72         if (optval)
73                 memcpy(*buf, optval, optlen);
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
109         *buf += sizeof(DHCP6Option);
110         *buflen -= sizeof(DHCP6Option);
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,
119                                       sizeof(addr->iaaddr));
120                 if (r < 0)
121                         return r;
122
123                 memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
124
125                 *buf += sizeof(addr->iaaddr);
126                 *buflen -= sizeof(addr->iaaddr);
127
128                 ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
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
138
139 static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode,
140                             size_t *optlen) {
141         DHCP6Option *option = (DHCP6Option*) *buf; /* unaligned! */
142         uint16_t len;
143
144         assert_return(buf, -EINVAL);
145         assert_return(optcode, -EINVAL);
146         assert_return(optlen, -EINVAL);
147
148         if (*buflen < sizeof(DHCP6Option))
149                 return -ENOMSG;
150
151         len = unaligned_read_be16(&option->len);
152
153         if (len > *buflen)
154                 return -ENOMSG;
155
156         *optcode = unaligned_read_be16(&option->code);
157         *optlen = len;
158
159         *buf += 4;
160         *buflen -= 4;
161
162         return 0;
163 }
164
165 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
166                        size_t *optlen, uint8_t **optvalue) {
167         int r;
168
169         assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
170
171         r = option_parse_hdr(buf, buflen, optcode, optlen);
172         if (r < 0)
173                 return r;
174
175         if (*optlen > *buflen)
176                 return -ENOBUFS;
177
178         *optvalue = *buf;
179         *buflen -= *optlen;
180         *buf += *optlen;
181
182         return 0;
183 }
184
185 int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
186                           DHCP6IA *ia) {
187         int r;
188         uint16_t opt, status;
189         size_t optlen;
190         size_t iaaddr_offset;
191         DHCP6Address *addr;
192         uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
193
194         assert_return(ia, -EINVAL);
195         assert_return(!ia->addresses, -EINVAL);
196
197         switch (iatype) {
198         case DHCP6_OPTION_IA_NA:
199
200                 if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) +
201                     sizeof(addr->iaaddr)) {
202                         r = -ENOBUFS;
203                         goto error;
204                 }
205
206                 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
207                 memcpy(&ia->id, *buf, iaaddr_offset);
208
209                 lt_t1 = be32toh(ia->lifetime_t1);
210                 lt_t2 = be32toh(ia->lifetime_t2);
211
212                 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
213                         log_dhcp6_client(client, "IA T1 %ds > T2 %ds",
214                                          lt_t1, lt_t2);
215                         r = -EINVAL;
216                         goto error;
217                 }
218
219                 break;
220
221         case DHCP6_OPTION_IA_TA:
222                 if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) +
223                     sizeof(addr->iaaddr)) {
224                         r = -ENOBUFS;
225                         goto error;
226                 }
227
228                 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
229                 memcpy(&ia->id, *buf, iaaddr_offset);
230
231                 ia->lifetime_t1 = 0;
232                 ia->lifetime_t2 = 0;
233
234                 break;
235
236         default:
237                 r = -ENOMSG;
238                 goto error;
239         }
240
241         ia->type = iatype;
242
243         *buflen -= iaaddr_offset;
244         *buf += iaaddr_offset;
245
246         while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
247
248                 switch (opt) {
249                 case DHCP6_OPTION_IAADDR:
250
251                         addr = new0(DHCP6Address, 1);
252                         if (!addr) {
253                                 r = -ENOMEM;
254                                 goto error;
255                         }
256
257                         LIST_INIT(addresses, addr);
258
259                         memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr));
260
261                         lt_valid = be32toh(addr->iaaddr.lifetime_valid);
262                         lt_pref = be32toh(addr->iaaddr.lifetime_valid);
263
264                         if (!lt_valid || lt_pref > lt_valid) {
265                                 log_dhcp6_client(client, "IA preferred %ds > valid %ds",
266                                                  lt_pref, lt_valid);
267                                 free(addr);
268                         } else {
269                                 LIST_PREPEND(addresses, ia->addresses, addr);
270                                 if (lt_valid < lt_min)
271                                         lt_min = lt_valid;
272                         }
273
274                         break;
275
276                 case DHCP6_OPTION_STATUS_CODE:
277                         if (optlen < sizeof(status))
278                                 break;
279
280                         status = (*buf)[0] << 8 | (*buf)[1];
281                         if (status) {
282                                 log_dhcp6_client(client, "IA status %d",
283                                                  status);
284                                 r = -EINVAL;
285                                 goto error;
286                         }
287
288                         break;
289
290                 default:
291                         log_dhcp6_client(client, "Unknown IA option %d", opt);
292                         break;
293                 }
294
295                 *buflen -= optlen;
296                 *buf += optlen;
297         }
298
299         if (r == -ENOMSG)
300                 r = 0;
301
302         if (!ia->lifetime_t1 && !ia->lifetime_t2) {
303                 lt_t1 = lt_min / 2;
304                 lt_t2 = lt_min / 10 * 8;
305                 ia->lifetime_t1 = htobe32(lt_t1);
306                 ia->lifetime_t2 = htobe32(lt_t2);
307
308                 log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero",
309                                  lt_t1, lt_t2);
310         }
311
312         if (*buflen)
313                 r = -ENOMSG;
314
315 error:
316         *buf += *buflen;
317         *buflen = 0;
318
319         return r;
320 }