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