tizen 2.4 release
[external/systemd.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 || optlen == 0, -EINVAL);
62
63         r = option_append_hdr(buf, buflen, code, optlen);
64         if (r < 0)
65                 return r;
66
67         if (optval)
68                 memcpy(*buf, optval, optlen);
69
70         *buf += optlen;
71         *buflen -= optlen;
72
73         return 0;
74 }
75
76 int 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
133
134 static 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
159 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
160                        size_t *optlen, uint8_t **optvalue) {
161         int r;
162
163         assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
164
165         r = option_parse_hdr(buf, buflen, optcode, optlen);
166         if (r < 0)
167                 return r;
168
169         if (*optlen > *buflen)
170                 return -ENOBUFS;
171
172         *optvalue = *buf;
173         *buflen -= *optlen;
174         *buf += *optlen;
175
176         return 0;
177 }
178
179 int 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
309 error:
310         *buf += *buflen;
311         *buflen = 0;
312
313         return r;
314 }