odhcp6c: add -K option to set packet kernel priority
[project/odhcp6c.git] / src / odhcp6c.c
1 /**
2 * Copyright (C) 2012-2014 Steven Barth <steven@midlink.org>
3 * Copyright (C) 2017-2018 Hans Dedecker <dedeckeh@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License v2 as published by
7 * the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 */
15
16 #include <time.h>
17 #include <errno.h>
18 #include <ctype.h>
19 #include <fcntl.h>
20 #include <limits.h>
21 #include <resolv.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stddef.h>
25 #include <unistd.h>
26 #include <syslog.h>
27 #include <signal.h>
28 #include <string.h>
29 #include <strings.h>
30 #include <stdbool.h>
31
32 #include <net/if.h>
33 #include <sys/syscall.h>
34 #include <arpa/inet.h>
35 #include <linux/if_addr.h>
36
37 #include "odhcp6c.h"
38 #include "ra.h"
39
40 #ifndef IN6_IS_ADDR_UNIQUELOCAL
41 #define IN6_IS_ADDR_UNIQUELOCAL(a) \
42 ((((__const uint32_t *) (a))[0] & htonl (0xfe000000)) \
43 == htonl (0xfc000000))
44 #endif
45 #define ARRAY_SEP " ,\t"
46
47 static void sighandler(int signal);
48 static int usage(void);
49 static int add_opt(const uint16_t code, const uint8_t *data,
50 const uint16_t len);
51 static int parse_opt_data(const char *data, uint8_t **dst,
52 const unsigned int type, const bool array);
53 static int parse_opt(const char *opt);
54
55 static uint8_t *state_data[_STATE_MAX] = {NULL};
56 static size_t state_len[_STATE_MAX] = {0};
57
58 static volatile bool signal_io = false;
59 static volatile bool signal_usr1 = false;
60 static volatile bool signal_usr2 = false;
61 static volatile bool signal_term = false;
62
63 static int urandom_fd = -1, allow_slaac_only = 0;
64 static bool bound = false, release = true, ra = false;
65 static time_t last_update = 0;
66 static char *ifname = NULL;
67
68 static unsigned int script_sync_delay = 10;
69 static unsigned int script_accu_delay = 1;
70
71 static struct odhcp6c_opt opts[] = {
72 { .code = DHCPV6_OPT_CLIENTID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
73 { .code = DHCPV6_OPT_SERVERID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
74 { .code = DHCPV6_OPT_IA_NA, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str= NULL },
75 { .code = DHCPV6_OPT_IA_TA, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
76 { .code = DHCPV6_OPT_IA_ADDR, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
77 { .code = DHCPV6_OPT_ORO, .flags = OPT_INTERNAL, .str = NULL },
78 { .code = DHCPV6_OPT_PREF, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
79 { .code = DHCPV6_OPT_ELAPSED, .flags = OPT_INTERNAL, .str = NULL },
80 { .code = DHCPV6_OPT_RELAY_MSG, .flags = OPT_INTERNAL, .str = NULL },
81 { .code = DHCPV6_OPT_AUTH, .flags = OPT_U8 | OPT_NO_PASSTHRU, .str = "authentication" },
82 { .code = DHCPV6_OPT_UNICAST, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
83 { .code = DHCPV6_OPT_STATUS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
84 { .code = DHCPV6_OPT_RAPID_COMMIT, .flags = OPT_INTERNAL, .str = NULL },
85 { .code = DHCPV6_OPT_USER_CLASS, .flags = OPT_USER_CLASS | OPT_ARRAY, .str = "userclass" },
86 { .code = DHCPV6_OPT_VENDOR_CLASS, .flags = OPT_U8, .str = "vendorclass" },
87 { .code = DHCPV6_OPT_INTERFACE_ID, .flags = OPT_INTERNAL, .str = NULL },
88 { .code = DHCPV6_OPT_RECONF_MESSAGE, .flags = OPT_INTERNAL, .str = NULL },
89 { .code = DHCPV6_OPT_RECONF_ACCEPT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
90 { .code = DHCPV6_OPT_SIP_SERVER_D, .flags = OPT_DNS_STR | OPT_ORO, .str = "sipserver_d" },
91 { .code = DHCPV6_OPT_SIP_SERVER_A, .flags = OPT_IP6 | OPT_ARRAY | OPT_ORO, .str = "sipserver_a" },
92 { .code = DHCPV6_OPT_DNS_SERVERS, .flags = OPT_IP6 | OPT_ARRAY | OPT_ORO, .str = "dns" },
93 { .code = DHCPV6_OPT_DNS_DOMAIN, .flags = OPT_DNS_STR | OPT_ORO, .str = "search" },
94 { .code = DHCPV6_OPT_IA_PD, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
95 { .code = DHCPV6_OPT_IA_PREFIX, .flags = OPT_INTERNAL, .str = NULL },
96 { .code = DHCPV6_OPT_SNTP_SERVERS, .flags = OPT_IP6 | OPT_ARRAY | OPT_ORO, .str = "sntpservers" },
97 { .code = DHCPV6_OPT_INFO_REFRESH, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO | OPT_ORO_STATELESS, .str = NULL },
98 { .code = DHCPV6_OPT_REMOTE_ID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
99 { .code = DHCPV6_OPT_SUBSCRIBER_ID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
100 { .code = DHCPV6_OPT_FQDN, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO, .str = NULL },
101 { .code = DHCPV6_OPT_ERO, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
102 { .code = DHCPV6_OPT_LQ_QUERY, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
103 { .code = DHCPV6_OPT_CLIENT_DATA, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
104 { .code = DHCPV6_OPT_CLT_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
105 { .code = DHCPV6_OPT_LQ_RELAY_DATA, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
106 { .code = DHCPV6_OPT_LQ_CLIENT_LINK, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
107 { .code = DHCPV6_OPT_RELAY_ID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
108 { .code = DHCPV6_OPT_NTP_SERVER, .flags = OPT_U8 | OPT_ORO, .str = "ntpserver" },
109 { .code = DHCPV6_OPT_CLIENT_ARCH_TYPE, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
110 { .code = DHCPV6_OPT_AFTR_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO, .str = NULL },
111 { .code = DHCPV6_OPT_RSOO, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
112 { .code = DHCPV6_OPT_PD_EXCLUDE, .flags = OPT_INTERNAL | OPT_ORO | OPT_ORO_STATEFUL, .str = NULL },
113 { .code = DHCPV6_OPT_VSS, .flags = OPT_U8, .str = "vss" },
114 { .code = DHCPV6_OPT_LINK_LAYER_ADDRESS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
115 { .code = DHCPV6_OPT_LINK_ADDRESS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
116 { .code = DHCPV6_OPT_RADIUS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
117 { .code = DHCPV6_OPT_SOL_MAX_RT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO | OPT_ORO_SOLICIT, .str = NULL },
118 { .code = DHCPV6_OPT_INF_MAX_RT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO | OPT_ORO_STATELESS, .str = NULL },
119 #ifdef EXT_CER_ID
120 { .code = DHCPV6_OPT_CER_ID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
121 #endif
122 { .code = DHCPV6_OPT_DHCPV4_MSG, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
123 { .code = DHCPV6_OPT_S46_RULE, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
124 { .code = DHCPV6_OPT_S46_BR, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
125 { .code = DHCPV6_OPT_S46_DMR, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
126 { .code = DHCPV6_OPT_S46_V4V6BIND, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
127 { .code = DHCPV6_OPT_S46_PORTPARAMS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
128 { .code = DHCPV6_OPT_S46_CONT_MAPE, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO, .str = NULL },
129 { .code = DHCPV6_OPT_S46_CONT_MAPT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO, .str = NULL },
130 { .code = DHCPV6_OPT_S46_CONT_LW, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU | OPT_ORO, .str = NULL },
131 { .code = DHCPV6_OPT_LQ_BASE_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
132 { .code = DHCPV6_OPT_LQ_START_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
133 { .code = DHCPV6_OPT_LQ_END_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
134 { .code = DHCPV6_OPT_ANI_ATT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
135 { .code = DHCPV6_OPT_ANI_NETWORK_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
136 { .code = DHCPV6_OPT_ANI_AP_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
137 { .code = DHCPV6_OPT_ANI_AP_BSSID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
138 { .code = DHCPV6_OPT_ANI_OPERATOR_ID, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
139 { .code = DHCPV6_OPT_ANI_OPERATOR_REALM, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
140 { .code = DHCPV6_OPT_MUD_URL_V6, .flags = OPT_STR | OPT_NO_PASSTHRU, .str = "mud_url_v6" },
141 { .code = DHCPV6_OPT_F_BINDING_STATUS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
142 { .code = DHCPV6_OPT_F_CONNECT_FLAGS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
143 { .code = DHCPV6_OPT_F_DNS_REMOVAL_INFO, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
144 { .code = DHCPV6_OPT_F_DNS_HOST_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
145 { .code = DHCPV6_OPT_F_DNS_ZONE_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
146 { .code = DHCPV6_OPT_F_DNS_FLAGS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
147 { .code = DHCPV6_OPT_F_EXPIRATION_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
148 { .code = DHCPV6_OPT_F_MAX_UNACKED_BNDUPD, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
149 { .code = DHCPV6_OPT_F_MCLT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
150 { .code = DHCPV6_OPT_F_PARTNER_LIFETIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
151 { .code = DHCPV6_OPT_F_PARTNER_LIFETIME_SENT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
152 { .code = DHCPV6_OPT_F_PARTNER_DOWN_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
153 { .code = DHCPV6_OPT_F_PARTNER_RAW_CLT_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
154 { .code = DHCPV6_OPT_F_PROTOCOL_VERSION, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
155 { .code = DHCPV6_OPT_F_KEEPALIVE_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
156 { .code = DHCPV6_OPT_F_RECONFIGURE_DATA, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
157 { .code = DHCPV6_OPT_F_RELATIONSHIP_NAME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
158 { .code = DHCPV6_OPT_F_SERVER_FLAGS, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
159 { .code = DHCPV6_OPT_F_SERVER_STATE, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
160 { .code = DHCPV6_OPT_F_START_TIME_OF_STATE, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
161 { .code = DHCPV6_OPT_F_STATE_EXPIRATION_TIME, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
162 { .code = DHCPV6_OPT_RELAY_PORT, .flags = OPT_INTERNAL | OPT_NO_PASSTHRU, .str = NULL },
163 { .code = 0, .flags = 0, .str = NULL },
164 };
165
166 int main(_unused int argc, char* const argv[])
167 {
168 static struct in6_addr ifid = IN6ADDR_ANY_INIT;
169 // Allocate resources
170 const char *pidfile = NULL;
171 const char *script = "/usr/sbin/odhcp6c-update";
172 ssize_t l;
173 uint8_t buf[134], *o_data;
174 char *optpos;
175 uint16_t opttype;
176 enum odhcp6c_ia_mode ia_na_mode = IA_MODE_TRY;
177 enum odhcp6c_ia_mode ia_pd_mode = IA_MODE_NONE;
178 bool stateful_only_mode = 0;
179 struct odhcp6c_opt *opt;
180 int ia_pd_iaid_index = 0;
181 int sk_prio = 0;
182 int sol_timeout = DHCPV6_SOL_MAX_RT;
183 int verbosity = 0;
184 bool help = false, daemonize = false;
185 int logopt = LOG_PID;
186 int c, res;
187 unsigned int client_options = DHCPV6_CLIENT_FQDN | DHCPV6_ACCEPT_RECONFIGURE;
188 unsigned int ra_options = RA_RDNSS_DEFAULT_LIFETIME;
189 unsigned int ra_holdoff_interval = RA_MIN_ADV_INTERVAL;
190
191 while ((c = getopt(argc, argv, "S::DN:V:P:FB:c:i:r:Ru:Ux:s:kK:t:m:Lhedp:fav")) != -1) {
192 switch (c) {
193 case 'S':
194 allow_slaac_only = (optarg) ? atoi(optarg) : -1;
195 break;
196
197 case 'D':
198 stateful_only_mode = 1;
199 break;
200
201 case 'N':
202 if (!strcmp(optarg, "force")) {
203 ia_na_mode = IA_MODE_FORCE;
204 allow_slaac_only = -1;
205 } else if (!strcmp(optarg, "none"))
206 ia_na_mode = IA_MODE_NONE;
207 else if (!strcmp(optarg, "try"))
208 ia_na_mode = IA_MODE_TRY;
209 else
210 help = true;
211 break;
212
213 case 'V':
214 opt = odhcp6c_find_opt(DHCPV6_OPT_VENDOR_CLASS);
215 if (!opt) {
216 syslog(LOG_ERR, "Failed to set vendor-class option");
217 return 1;
218 }
219
220 o_data = NULL;
221 res = parse_opt_data(optarg, &o_data, opt->flags & OPT_MASK_SIZE,
222 (opt->flags & OPT_ARRAY) == OPT_ARRAY);
223 if (res > 0) {
224 res = add_opt(opt->code, o_data, res);
225 if (res) {
226 if (res > 0)
227 return 1;
228
229 help = true;
230 }
231 } else
232 help = true;
233
234 free(o_data);
235 break;
236
237 case 'P':
238 if (ia_pd_mode == IA_MODE_NONE)
239 ia_pd_mode = IA_MODE_TRY;
240
241 if (allow_slaac_only >= 0 && allow_slaac_only < 10)
242 allow_slaac_only = 10;
243
244 char *iaid_begin;
245 int iaid_len = 0;
246 int prefix_length = strtoul(optarg, &iaid_begin, 10);
247
248 if (*iaid_begin != '\0' && *iaid_begin != ',' && *iaid_begin != ':') {
249 syslog(LOG_ERR, "invalid argument: '%s'", optarg);
250 return 1;
251 }
252
253 struct odhcp6c_request_prefix prefix = { 0, prefix_length };
254
255 if (*iaid_begin == ',' && (iaid_len = strlen(iaid_begin)) > 1)
256 memcpy(&prefix.iaid, iaid_begin + 1, iaid_len > 4 ? 4 : iaid_len);
257 else if (*iaid_begin == ':')
258 prefix.iaid = htonl((uint32_t)strtoul(&iaid_begin[1], NULL, 16));
259 else
260 prefix.iaid = htonl(++ia_pd_iaid_index);
261
262 if (odhcp6c_add_state(STATE_IA_PD_INIT, &prefix, sizeof(prefix))) {
263 syslog(LOG_ERR, "Failed to set request IPv6-Prefix");
264 return 1;
265 }
266 break;
267
268 case 'F':
269 allow_slaac_only = -1;
270 ia_pd_mode = IA_MODE_FORCE;
271 break;
272
273 case 'c':
274 l = script_unhexlify(&buf[4], sizeof(buf) - 4, optarg);
275 if (l > 0) {
276 buf[0] = 0;
277 buf[1] = DHCPV6_OPT_CLIENTID;
278 buf[2] = 0;
279 buf[3] = l;
280 if (odhcp6c_add_state(STATE_CLIENT_ID, buf, l + 4)) {
281 syslog(LOG_ERR, "Failed to override client-ID");
282 return 1;
283 }
284 } else
285 help = true;
286 break;
287
288 case 'i':
289 if (inet_pton(AF_INET6, optarg, &ifid) != 1)
290 help = true;
291 break;
292
293 case 'r':
294 optpos = optarg;
295 while (optpos[0]) {
296 opttype = htons(strtoul(optarg, &optpos, 10));
297 if (optpos == optarg)
298 break;
299 else if (optpos[0])
300 optarg = &optpos[1];
301
302 if (odhcp6c_add_state(STATE_ORO, &opttype, 2)) {
303 syslog(LOG_ERR, "Failed to add requested option");
304 return 1;
305 }
306 }
307 break;
308
309 case 'R':
310 client_options |= DHCPV6_STRICT_OPTIONS;
311 break;
312
313 case 'u':
314 opt = odhcp6c_find_opt(DHCPV6_OPT_USER_CLASS);
315 if (!opt) {
316 syslog(LOG_ERR, "Failed to set user-class option");
317 return 1;
318 }
319
320 o_data = NULL;
321 res = parse_opt_data(optarg, &o_data, opt->flags & OPT_MASK_SIZE,
322 (opt->flags & OPT_ARRAY) == OPT_ARRAY);
323 if (res > 0) {
324 res = add_opt(opt->code, o_data, res);
325 if (res) {
326 if (res > 0)
327 return 1;
328
329 help = true;
330 }
331 } else
332 help = true;
333
334 free(o_data);
335 break;
336
337 case 'U':
338 client_options |= DHCPV6_IGNORE_OPT_UNICAST;
339 break;
340
341 case 's':
342 script = optarg;
343 break;
344
345 case 'k':
346 release = false;
347 break;
348
349 case 'K':
350 sk_prio = atoi(optarg);
351 break;
352
353 case 't':
354 sol_timeout = atoi(optarg);
355 break;
356
357 case 'm':
358 ra_holdoff_interval = atoi(optarg);
359 break;
360
361 case 'L':
362 ra_options &= ~RA_RDNSS_DEFAULT_LIFETIME;
363 break;
364
365 case 'e':
366 logopt |= LOG_PERROR;
367 break;
368
369 case 'd':
370 daemonize = true;
371 break;
372
373 case 'p':
374 pidfile = optarg;
375 break;
376
377 case 'f':
378 client_options &= ~DHCPV6_CLIENT_FQDN;
379 break;
380
381 case 'a':
382 client_options &= ~DHCPV6_ACCEPT_RECONFIGURE;
383 break;
384
385 case 'v':
386 ++verbosity;
387 break;
388
389 case 'x':
390 res = parse_opt(optarg);
391 if (res) {
392 if (res > 0)
393 return res;
394
395 help = true;
396 }
397 break;
398
399 default:
400 help = true;
401 break;
402 }
403 }
404
405 if (allow_slaac_only > 0)
406 script_sync_delay = allow_slaac_only;
407
408 openlog("odhcp6c", logopt, LOG_DAEMON);
409 if (!verbosity)
410 setlogmask(LOG_UPTO(LOG_WARNING));
411
412 ifname = argv[optind];
413
414 if (help || !ifname)
415 return usage();
416
417 signal(SIGIO, sighandler);
418 signal(SIGHUP, sighandler);
419 signal(SIGINT, sighandler);
420 signal(SIGTERM, sighandler);
421 signal(SIGUSR1, sighandler);
422 signal(SIGUSR2, sighandler);
423
424 if ((urandom_fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY)) < 0 ||
425 init_dhcpv6(ifname, client_options, sk_prio, sol_timeout) ||
426 ra_init(ifname, &ifid, ra_options, ra_holdoff_interval) ||
427 script_init(script, ifname)) {
428 syslog(LOG_ERR, "failed to initialize: %s", strerror(errno));
429 return 3;
430 }
431
432 if (daemonize) {
433 openlog("odhcp6c", LOG_PID, LOG_DAEMON); // Disable LOG_PERROR
434 if (daemon(0, 0)) {
435 syslog(LOG_ERR, "Failed to daemonize: %s",
436 strerror(errno));
437 return 4;
438 }
439
440 if (!pidfile) {
441 snprintf((char*)buf, sizeof(buf), "/var/run/odhcp6c.%s.pid", ifname);
442 pidfile = (char*)buf;
443 }
444
445 FILE *fp = fopen(pidfile, "w");
446 if (fp) {
447 fprintf(fp, "%i\n", getpid());
448 fclose(fp);
449 }
450 }
451
452 script_call("started", 0, false);
453
454 while (!signal_term) { // Main logic
455 odhcp6c_clear_state(STATE_SERVER_ID);
456 odhcp6c_clear_state(STATE_SERVER_ADDR);
457 odhcp6c_clear_state(STATE_IA_NA);
458 odhcp6c_clear_state(STATE_IA_PD);
459 odhcp6c_clear_state(STATE_SNTP_IP);
460 odhcp6c_clear_state(STATE_NTP_IP);
461 odhcp6c_clear_state(STATE_NTP_FQDN);
462 odhcp6c_clear_state(STATE_SIP_IP);
463 odhcp6c_clear_state(STATE_SIP_FQDN);
464 bound = false;
465
466 syslog(LOG_NOTICE, "(re)starting transaction on %s", ifname);
467
468 signal_usr1 = signal_usr2 = false;
469 int mode = dhcpv6_set_ia_mode(ia_na_mode, ia_pd_mode, stateful_only_mode);
470 if (mode != DHCPV6_STATELESS)
471 mode = dhcpv6_request(DHCPV6_MSG_SOLICIT);
472
473 odhcp6c_signal_process();
474
475 if (mode < 0)
476 continue;
477
478 do {
479 res = dhcpv6_request(mode == DHCPV6_STATELESS ?
480 DHCPV6_MSG_INFO_REQ : DHCPV6_MSG_REQUEST);
481 bool signalled = odhcp6c_signal_process();
482
483 if (res > 0)
484 break;
485 else if (signalled) {
486 mode = -1;
487 break;
488 }
489
490 mode = dhcpv6_promote_server_cand();
491 } while (mode > DHCPV6_UNKNOWN);
492
493 if (mode < 0)
494 continue;
495
496 switch (mode) {
497 case DHCPV6_STATELESS:
498 bound = true;
499 syslog(LOG_NOTICE, "entering stateless-mode on %s", ifname);
500
501 while (!signal_usr2 && !signal_term) {
502 signal_usr1 = false;
503 script_call("informed", script_sync_delay, true);
504
505 res = dhcpv6_poll_reconfigure();
506 odhcp6c_signal_process();
507
508 if (res > 0)
509 continue;
510
511 if (signal_usr1) {
512 signal_usr1 = false; // Acknowledged
513 continue;
514 }
515
516 if (signal_usr2 || signal_term)
517 break;
518
519 res = dhcpv6_request(DHCPV6_MSG_INFO_REQ);
520 odhcp6c_signal_process();
521
522 if (signal_usr1)
523 continue;
524 else if (res < 0)
525 break;
526 }
527 break;
528
529 case DHCPV6_STATEFUL:
530 bound = true;
531 script_call("bound", script_sync_delay, true);
532 syslog(LOG_NOTICE, "entering stateful-mode on %s", ifname);
533
534 while (!signal_usr2 && !signal_term) {
535 // Renew Cycle
536 // Wait for T1 to expire or until we get a reconfigure
537 res = dhcpv6_poll_reconfigure();
538 odhcp6c_signal_process();
539 if (res > 0) {
540 script_call("updated", 0, false);
541 continue;
542 }
543
544 // Handle signal, if necessary
545 if (signal_usr1)
546 signal_usr1 = false; // Acknowledged
547
548 if (signal_usr2 || signal_term)
549 break; // Other signal type
550
551 // Send renew as T1 expired
552 res = dhcpv6_request(DHCPV6_MSG_RENEW);
553 odhcp6c_signal_process();
554
555 if (res > 0) { // Renew was succesfull
556 // Publish updates
557 script_call("updated", 0, false);
558 continue; // Renew was successful
559 }
560
561 odhcp6c_clear_state(STATE_SERVER_ID); // Remove binding
562 odhcp6c_clear_state(STATE_SERVER_ADDR);
563
564 size_t ia_pd_len, ia_na_len;
565 odhcp6c_get_state(STATE_IA_PD, &ia_pd_len);
566 odhcp6c_get_state(STATE_IA_NA, &ia_na_len);
567
568 if (ia_pd_len == 0 && ia_na_len == 0)
569 break;
570
571 // If we have IAs, try rebind otherwise restart
572 res = dhcpv6_request(DHCPV6_MSG_REBIND);
573 odhcp6c_signal_process();
574
575 if (res > 0)
576 script_call("rebound", 0, true);
577 else
578 break;
579 }
580 break;
581
582 default:
583 break;
584 }
585
586 odhcp6c_expire(false);
587
588 size_t ia_pd_len, ia_na_len, server_id_len;
589 odhcp6c_get_state(STATE_IA_PD, &ia_pd_len);
590 odhcp6c_get_state(STATE_IA_NA, &ia_na_len);
591 odhcp6c_get_state(STATE_SERVER_ID, &server_id_len);
592
593 // Add all prefixes to lost prefixes
594 bound = false;
595 script_call("unbound", 0, true);
596
597 if (server_id_len > 0 && (ia_pd_len > 0 || ia_na_len > 0) && release)
598 dhcpv6_request(DHCPV6_MSG_RELEASE);
599
600 odhcp6c_clear_state(STATE_IA_NA);
601 odhcp6c_clear_state(STATE_IA_PD);
602 }
603
604 script_call("stopped", 0, true);
605
606 return 0;
607 }
608
609 static int usage(void)
610 {
611 const char buf[] =
612 "Usage: odhcp6c [options] <interface>\n"
613 "\nFeature options:\n"
614 " -S <time> Wait at least <time> sec for a DHCP-server (0)\n"
615 " -D Discard advertisements without any address or prefix proposed\n"
616 " -N <mode> Mode for requesting addresses [try|force|none]\n"
617 " -P <length> Request IPv6-Prefix (0 = auto)\n"
618 " -F Force IPv6-Prefix\n"
619 " -V <class> Set vendor-class option (base-16 encoded)\n"
620 " -u <user-class> Set user-class option string\n"
621 " -x <opt>:<val> Add option opt (with value val) in sent packets (cumulative)\n"
622 " Examples of IPv6 address, string and base-16 encoded options:\n"
623 " -x dns:2001:2001::1,2001:2001::2 - option 23\n"
624 " -x 15:office - option 15 (userclass)\n"
625 " -x 0x1f4:ABBA - option 500\n"
626 " -x 202:'\"file\"' - option 202\n"
627 " -c <clientid> Override client-ID (base-16 encoded 16-bit type + value)\n"
628 " -i <iface-id> Use a custom interface identifier for RA handling\n"
629 " -r <options> Options to be requested (comma-separated)\n"
630 " -R Do not request any options except those specified with -r\n"
631 " -s <script> Status update script (/usr/sbin/odhcp6c-update)\n"
632 " -a Don't send Accept Reconfigure option\n"
633 " -f Don't send Client FQDN option\n"
634 " -k Don't send a RELEASE when stopping\n"
635 " -K <sk-prio> Set packet kernel priority (0)\n"
636 " -t <seconds> Maximum timeout for DHCPv6-SOLICIT (120)\n"
637 " -m <seconds> Minimum time between accepting RA updates (3)\n"
638 " -L Ignore default lifetime for RDNSS records\n"
639 " -U Ignore Server Unicast option\n"
640 "\nInvocation options:\n"
641 " -p <pidfile> Set pidfile (/var/run/odhcp6c.pid)\n"
642 " -d Daemonize\n"
643 " -e Write logmessages to stderr\n"
644 " -v Increase logging verbosity\n"
645 " -h Show this help\n\n";
646 fputs(buf, stderr);
647
648 return 1;
649 }
650
651 // Don't want to pull-in librt and libpthread just for a monotonic clock...
652 uint64_t odhcp6c_get_milli_time(void)
653 {
654 struct timespec t;
655
656 clock_gettime(CLOCK_MONOTONIC, &t);
657
658 return ((uint64_t)t.tv_sec) * 1000 + ((uint64_t)t.tv_nsec) / 1000000;
659 }
660
661 static uint8_t* odhcp6c_resize_state(enum odhcp6c_state state, ssize_t len)
662 {
663 if (len == 0)
664 return state_data[state] + state_len[state];
665 else if (state_len[state] + len > 1024)
666 return NULL;
667
668 uint8_t *n = realloc(state_data[state], state_len[state] + len);
669
670 if (n || state_len[state] + len == 0) {
671 state_data[state] = n;
672 n += state_len[state];
673 state_len[state] += len;
674 }
675
676 return n;
677 }
678
679 bool odhcp6c_signal_process(void)
680 {
681 while (signal_io) {
682 signal_io = false;
683
684 bool ra_updated = ra_process();
685
686 if (ra_link_up()) {
687 signal_usr2 = true;
688 ra = false;
689 }
690
691 if (ra_updated && (bound || allow_slaac_only >= 0)) {
692 script_call("ra-updated", (!ra && !bound) ?
693 script_sync_delay : script_accu_delay, false);
694 ra = true;
695 }
696 }
697
698 return signal_usr1 || signal_usr2 || signal_term;
699 }
700
701 void odhcp6c_clear_state(enum odhcp6c_state state)
702 {
703 state_len[state] = 0;
704 }
705
706 int odhcp6c_add_state(enum odhcp6c_state state, const void *data, size_t len)
707 {
708 uint8_t *n = odhcp6c_resize_state(state, len);
709
710 if (!n)
711 return -1;
712
713 memcpy(n, data, len);
714
715 return 0;
716 }
717
718 int odhcp6c_insert_state(enum odhcp6c_state state, size_t offset, const void *data, size_t len)
719 {
720 ssize_t len_after = state_len[state] - offset;
721 if (len_after < 0)
722 return -1;
723
724 uint8_t *n = odhcp6c_resize_state(state, len);
725
726 if (n) {
727 uint8_t *sdata = state_data[state];
728
729 memmove(sdata + offset + len, sdata + offset, len_after);
730 memcpy(sdata + offset, data, len);
731 }
732
733 return 0;
734 }
735
736 size_t odhcp6c_remove_state(enum odhcp6c_state state, size_t offset, size_t len)
737 {
738 uint8_t *data = state_data[state];
739 ssize_t len_after = state_len[state] - (offset + len);
740
741 if (len_after < 0)
742 return state_len[state];
743
744 memmove(data + offset, data + offset + len, len_after);
745
746 return state_len[state] -= len;
747 }
748
749 void* odhcp6c_move_state(enum odhcp6c_state state, size_t *len)
750 {
751 *len = state_len[state];
752 void *data = state_data[state];
753
754 state_len[state] = 0;
755 state_data[state] = NULL;
756
757 return data;
758 }
759
760 void* odhcp6c_get_state(enum odhcp6c_state state, size_t *len)
761 {
762 *len = state_len[state];
763
764 return state_data[state];
765 }
766
767 static struct odhcp6c_entry* odhcp6c_find_entry(enum odhcp6c_state state, const struct odhcp6c_entry *new)
768 {
769 size_t len, cmplen = offsetof(struct odhcp6c_entry, target) + ((new->length + 7) / 8);
770 uint8_t *start = odhcp6c_get_state(state, &len);
771
772 for (struct odhcp6c_entry *c = (struct odhcp6c_entry*)start;
773 (uint8_t*)c < &start[len] &&
774 (uint8_t*)odhcp6c_next_entry(c) <= &start[len];
775 c = odhcp6c_next_entry(c)) {
776 if (!memcmp(c, new, cmplen) && !memcmp(c->auxtarget, new->auxtarget, new->auxlen))
777 return c;
778 }
779
780 return NULL;
781 }
782
783 bool odhcp6c_update_entry(enum odhcp6c_state state, struct odhcp6c_entry *new,
784 uint32_t safe, unsigned int holdoff_interval)
785 {
786 struct odhcp6c_entry *x = odhcp6c_find_entry(state, new);
787
788 if (x && x->valid > new->valid && new->valid < safe)
789 new->valid = safe;
790
791 if (x) {
792 if (holdoff_interval && new->valid >= x->valid &&
793 new->valid != UINT32_MAX &&
794 new->valid - x->valid < holdoff_interval &&
795 new->preferred >= x->preferred &&
796 new->preferred != UINT32_MAX &&
797 new->preferred - x->preferred < holdoff_interval)
798 return false;
799
800 x->valid = new->valid;
801 x->preferred = new->preferred;
802 x->t1 = new->t1;
803 x->t2 = new->t2;
804 x->iaid = new->iaid;
805 } else if (odhcp6c_add_state(state, new, odhcp6c_entry_size(new)))
806 return false;
807
808 return true;
809 }
810
811 static void odhcp6c_expire_list(enum odhcp6c_state state, uint32_t elapsed, bool remove_expired)
812 {
813 size_t len;
814 uint8_t *start = odhcp6c_get_state(state, &len);
815
816 for (struct odhcp6c_entry *c = (struct odhcp6c_entry*)start;
817 (uint8_t*)c < &start[len] &&
818 (uint8_t*)odhcp6c_next_entry(c) <= &start[len];
819 ) {
820 if (c->t1 < elapsed)
821 c->t1 = 0;
822 else if (c->t1 != UINT32_MAX)
823 c->t1 -= elapsed;
824
825 if (c->t2 < elapsed)
826 c->t2 = 0;
827 else if (c->t2 != UINT32_MAX)
828 c->t2 -= elapsed;
829
830 if (c->preferred < elapsed)
831 c->preferred = 0;
832 else if (c->preferred != UINT32_MAX)
833 c->preferred -= elapsed;
834
835 if (c->valid < elapsed)
836 c->valid = 0;
837 else if (c->valid != UINT32_MAX)
838 c->valid -= elapsed;
839
840 if (!c->valid && remove_expired) {
841 odhcp6c_remove_state(state, ((uint8_t*)c) - start, odhcp6c_entry_size(c));
842 start = odhcp6c_get_state(state, &len);
843 } else
844 c = odhcp6c_next_entry(c);
845 }
846 }
847
848 static uint8_t *odhcp6c_state_find_opt(const uint16_t code)
849 {
850 size_t opts_len;
851 uint8_t *odata, *opts = odhcp6c_get_state(STATE_OPTS, &opts_len);
852 uint16_t otype, olen;
853
854 dhcpv6_for_each_option(opts, &opts[opts_len], otype, olen, odata) {
855 if (otype == code)
856 return &odata[-4];
857 }
858
859 return NULL;
860 }
861
862 void odhcp6c_expire(bool expire_ia_pd)
863 {
864 time_t now = odhcp6c_get_milli_time() / 1000;
865 uint32_t elapsed = (last_update > 0) ? now - last_update : 0;
866
867 last_update = now;
868
869 odhcp6c_expire_list(STATE_RA_PREFIX, elapsed, true);
870 odhcp6c_expire_list(STATE_RA_ROUTE, elapsed, true);
871 odhcp6c_expire_list(STATE_RA_DNS, elapsed, true);
872 odhcp6c_expire_list(STATE_RA_SEARCH, elapsed, true);
873 odhcp6c_expire_list(STATE_IA_NA, elapsed, true);
874 odhcp6c_expire_list(STATE_IA_PD, elapsed, expire_ia_pd);
875 }
876
877 uint32_t odhcp6c_elapsed(void)
878 {
879 return odhcp6c_get_milli_time() / 1000 - last_update;
880 }
881
882 int odhcp6c_random(void *buf, size_t len)
883 {
884 return read(urandom_fd, buf, len);
885 }
886
887 bool odhcp6c_is_bound(void)
888 {
889 return bound;
890 }
891
892 bool odhcp6c_addr_in_scope(const struct in6_addr *addr)
893 {
894 FILE *fd = fopen("/proc/net/if_inet6", "r");
895 int len;
896 bool ret = false;
897 char buf[256];
898
899 if (fd == NULL)
900 return false;
901
902 while (fgets(buf, sizeof(buf), fd)) {
903 struct in6_addr inet6_addr;
904 uint32_t flags, dummy;
905 unsigned int i;
906 char name[IF_NAMESIZE], addr_buf[33];
907
908 len = strlen(buf);
909
910 if ((len <= 0) || buf[len - 1] != '\n')
911 break;
912
913 buf[--len] = '\0';
914
915 if (sscanf(buf, "%s %x %x %x %x %s",
916 addr_buf, &dummy, &dummy, &dummy, &flags, name) != 6)
917 break;
918
919 if (strcmp(name, ifname) ||
920 (flags & (IFA_F_DADFAILED | IFA_F_TENTATIVE | IFA_F_DEPRECATED)))
921 continue;
922
923 for (i = 0; i < strlen(addr_buf); i++) {
924 if (!isxdigit(addr_buf[i]) || isupper(addr_buf[i]))
925 break;
926 }
927
928 memset(&inet6_addr, 0, sizeof(inet6_addr));
929 for (i = 0; i < (strlen(addr_buf) / 2); i++) {
930 unsigned char byte;
931 static const char hex[] = "0123456789abcdef";
932 byte = ((index(hex, addr_buf[i * 2]) - hex) << 4) |
933 (index(hex, addr_buf[i * 2 + 1]) - hex);
934 inet6_addr.s6_addr[i] = byte;
935 }
936
937 if ((IN6_IS_ADDR_LINKLOCAL(&inet6_addr) == IN6_IS_ADDR_LINKLOCAL(addr)) &&
938 (IN6_IS_ADDR_UNIQUELOCAL(&inet6_addr) == IN6_IS_ADDR_UNIQUELOCAL(addr))) {
939 ret = true;
940 break;
941 }
942 }
943
944 fclose(fd);
945 return ret;
946 }
947
948 static void sighandler(int signal)
949 {
950 if (signal == SIGUSR1)
951 signal_usr1 = true;
952 else if (signal == SIGUSR2)
953 signal_usr2 = true;
954 else if (signal == SIGIO)
955 signal_io = true;
956 else
957 signal_term = true;
958 }
959
960 static int add_opt(const uint16_t code, const uint8_t *data, const uint16_t len)
961 {
962 struct {
963 uint16_t code;
964 uint16_t len;
965 } opt_hdr = { htons(code), htons(len) };
966
967 if (odhcp6c_state_find_opt(code))
968 return -1;
969
970 if (odhcp6c_add_state(STATE_OPTS, &opt_hdr, sizeof(opt_hdr)) ||
971 odhcp6c_add_state(STATE_OPTS, data, len)) {
972 syslog(LOG_ERR, "Failed to add option %hu", code);
973 return 1;
974 }
975
976 return 0;
977 }
978
979 struct odhcp6c_opt *odhcp6c_find_opt(const uint16_t code)
980 {
981 struct odhcp6c_opt *opt = opts;
982
983 while (opt->code) {
984 if (opt->code == code)
985 return opt;
986
987 opt++;
988 }
989
990 return NULL;
991 }
992
993 static struct odhcp6c_opt *odhcp6c_find_opt_by_name(const char *name)
994 {
995 struct odhcp6c_opt *opt = opts;
996
997 if (!name || !strlen(name))
998 return NULL;
999
1000 while (opt->code && (!opt->str || strcmp(opt->str, name)))
1001 opt++;
1002
1003 return (opt->code > 0 ? opt : NULL);
1004 }
1005
1006 static int parse_opt_u8(const char *src, uint8_t **dst)
1007 {
1008 int len = strlen(src);
1009
1010 *dst = realloc(*dst, len/2);
1011 if (!*dst)
1012 return -1;
1013
1014 return script_unhexlify(*dst, len, src);
1015 }
1016
1017 static int parse_opt_string(const char *src, uint8_t **dst, const bool array)
1018 {
1019 int o_len = 0;
1020 char *sep = strpbrk(src, ARRAY_SEP);
1021
1022 if (sep && !array)
1023 return -1;
1024
1025 do {
1026 if (sep) {
1027 *sep = 0;
1028 sep++;
1029 }
1030
1031 int len = strlen(src);
1032
1033 *dst = realloc(*dst, o_len + len);
1034 if (!*dst)
1035 return -1;
1036
1037 memcpy(&((*dst)[o_len]), src, len);
1038
1039 o_len += len;
1040 src = sep;
1041
1042 if (sep)
1043 sep = strpbrk(src, ARRAY_SEP);
1044 } while (src);
1045
1046 return o_len;
1047 }
1048
1049 static int parse_opt_dns_string(const char *src, uint8_t **dst, const bool array)
1050 {
1051 int o_len = 0;
1052 char *sep = strpbrk(src, ARRAY_SEP);
1053
1054 if (sep && !array)
1055 return -1;
1056
1057 do {
1058 uint8_t tmp[256];
1059
1060 if (sep) {
1061 *sep = 0;
1062 sep++;
1063 }
1064
1065 int len = dn_comp(src, tmp, sizeof(tmp), NULL, NULL);
1066 if (len < 0)
1067 return -1;
1068
1069 *dst = realloc(*dst, o_len + len);
1070 if (!*dst)
1071 return -1;
1072
1073 memcpy(&((*dst)[o_len]), tmp, len);
1074
1075 o_len += len;
1076 src = sep;
1077
1078 if (sep)
1079 sep = strpbrk(src, ARRAY_SEP);
1080 } while (src);
1081
1082 return o_len;
1083 }
1084
1085 static int parse_opt_ip6(const char *src, uint8_t **dst, const bool array)
1086 {
1087 int o_len = 0;
1088 char *sep = strpbrk(src, ARRAY_SEP);
1089
1090 if (sep && !array)
1091 return -1;
1092
1093 do {
1094 int len = sizeof(struct in6_addr);
1095
1096 if (sep) {
1097 *sep = 0;
1098 sep++;
1099 }
1100
1101 *dst = realloc(*dst, o_len + len);
1102 if (!*dst)
1103 return -1;
1104
1105 if (inet_pton(AF_INET6, src, &((*dst)[o_len])) < 1)
1106 return -1;
1107
1108 o_len += len;
1109 src = sep;
1110
1111 if (sep)
1112 sep = strpbrk(src, ARRAY_SEP);
1113 } while (src);
1114
1115 return o_len;
1116 }
1117
1118 static int parse_opt_user_class(const char *src, uint8_t **dst, const bool array)
1119 {
1120 int o_len = 0;
1121 char *sep = strpbrk(src, ARRAY_SEP);
1122
1123 if (sep && !array)
1124 return -1;
1125
1126 do {
1127 if (sep) {
1128 *sep = 0;
1129 sep++;
1130 }
1131 uint16_t str_len = strlen(src);
1132
1133 *dst = realloc(*dst, o_len + str_len + 2);
1134 if (!*dst)
1135 return -1;
1136
1137 struct user_class {
1138 uint16_t len;
1139 uint8_t data[];
1140 } *e = (struct user_class *)&((*dst)[o_len]);
1141
1142 e->len = ntohs(str_len);
1143 memcpy(e->data, src, str_len);
1144
1145 o_len += str_len + 2;
1146 src = sep;
1147
1148 if (sep)
1149 sep = strpbrk(src, ARRAY_SEP);
1150 } while (src);
1151
1152 return o_len;
1153 }
1154
1155 static int parse_opt_data(const char *data, uint8_t **dst, const unsigned int type,
1156 const bool array)
1157 {
1158 int ret = 0;
1159
1160 switch (type) {
1161 case OPT_U8:
1162 ret = parse_opt_u8(data, dst);
1163 break;
1164
1165 case OPT_STR:
1166 ret = parse_opt_string(data, dst, array);
1167 break;
1168
1169 case OPT_DNS_STR:
1170 ret = parse_opt_dns_string(data, dst, array);
1171 break;
1172
1173 case OPT_IP6:
1174 ret = parse_opt_ip6(data, dst, array);
1175 break;
1176
1177 case OPT_USER_CLASS:
1178 ret = parse_opt_user_class(data, dst, array);
1179 break;
1180
1181 default:
1182 ret = -1;
1183 break;
1184 }
1185
1186 return ret;
1187 }
1188
1189 static int parse_opt(const char *opt)
1190 {
1191 uint32_t optn;
1192 char *data;
1193 uint8_t *payload = NULL;
1194 int payload_len;
1195 unsigned int type = OPT_U8;
1196 bool array = false;
1197 struct odhcp6c_opt *dopt = NULL;
1198 int ret = -1;
1199
1200 data = strpbrk(opt, ":");
1201 if (!data)
1202 return -1;
1203
1204 *data = '\0';
1205 data++;
1206
1207 if (strlen(opt) == 0 || strlen(data) == 0)
1208 return -1;
1209
1210 dopt = odhcp6c_find_opt_by_name(opt);
1211 if (!dopt) {
1212 char *e;
1213 optn = strtoul(opt, &e, 0);
1214 if (*e || e == opt || optn > USHRT_MAX)
1215 return -1;
1216
1217 dopt = odhcp6c_find_opt(optn);
1218 } else
1219 optn = dopt->code;
1220
1221 /* Check if the type for the content is well-known */
1222 if (dopt) {
1223 /* Refuse internal options */
1224 if (dopt->flags & OPT_INTERNAL)
1225 return -1;
1226
1227 type = dopt->flags & OPT_MASK_SIZE;
1228 array = ((dopt->flags & OPT_ARRAY) == OPT_ARRAY) ? true : false;
1229 } else if (data[0] == '"' || data[0] == '\'') {
1230 char *end = strrchr(data + 1, data[0]);
1231
1232 if (end && (end == (data + strlen(data) - 1))) {
1233 /* Raw option is specified as a string */
1234 type = OPT_STR;
1235 data++;
1236 *end = '\0';
1237 }
1238
1239 }
1240
1241 payload_len = parse_opt_data(data, &payload, type, array);
1242 if (payload_len > 0)
1243 ret = add_opt(optn, payload, payload_len);
1244
1245 free(payload);
1246
1247 return ret;
1248 }