chiark / gitweb /
rules: simplify mmc RPMB handling
[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 } _packed_ 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;
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         option->code = htobe16(optcode);
54         option->len = htobe16(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, size_t *optlen) {
140         DHCP6Option *option = (DHCP6Option*) *buf;
141         uint16_t len;
142
143         assert_return(buf, -EINVAL);
144         assert_return(optcode, -EINVAL);
145         assert_return(optlen, -EINVAL);
146
147         if (*buflen < sizeof(DHCP6Option))
148                 return -ENOMSG;
149
150         len = be16toh(option->len);
151
152         if (len > *buflen)
153                 return -ENOMSG;
154
155         *optcode = be16toh(option->code);
156         *optlen = len;
157
158         *buf += 4;
159         *buflen -= 4;
160
161         return 0;
162 }
163
164 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
165                        size_t *optlen, uint8_t **optvalue) {
166         int r;
167
168         assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
169
170         r = option_parse_hdr(buf, buflen, optcode, optlen);
171         if (r < 0)
172                 return r;
173
174         if (*optlen > *buflen)
175                 return -ENOBUFS;
176
177         *optvalue = *buf;
178         *buflen -= *optlen;
179         *buf += *optlen;
180
181         return 0;
182 }
183
184 int 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
199                 if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) +
200                     sizeof(addr->iaaddr)) {
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:
221                 if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) +
222                     sizeof(addr->iaaddr)) {
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
258                         memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr));
259
260                         lt_valid = be32toh(addr->iaaddr.lifetime_valid);
261                         lt_pref = be32toh(addr->iaaddr.lifetime_valid);
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
314 error:
315         *buf += *buflen;
316         *buflen = 0;
317
318         return r;
319 }