2 * Copyright (C) 2012-2014 Steven Barth <steven@midlink.org>
3 * Copyright (C) 2017-2018 Hans Dedecker <dedeckeh@gmail.com>
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.
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.
25 #include <arpa/inet.h>
27 #include <netinet/in.h>
31 static const char hexdigits
[] = "0123456789abcdef";
32 static const int8_t hexvals
[] = {
33 -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -2, -1, -1,
34 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
35 -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
36 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
37 -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
38 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
39 -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
40 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
43 static char action
[16] = "";
44 static char *argv
[4] = {NULL
, NULL
, action
, NULL
};
45 static volatile pid_t running
= 0;
46 static time_t started
;
48 static void script_sighandle(int signal
)
50 if (signal
== SIGCHLD
) {
53 while ((child
= waitpid(-1, NULL
, WNOHANG
)) > 0)
59 int script_init(const char *path
, const char *ifname
)
61 argv
[0] = (char*)path
;
62 argv
[1] = (char*)ifname
;
63 signal(SIGCHLD
, script_sighandle
);
68 ssize_t
script_unhexlify(uint8_t *dst
, size_t len
, const char *src
)
72 for (c
= 0; c
< len
&& src
[0] && src
[1]; ++c
) {
73 int8_t x
= (int8_t)*src
++;
74 int8_t y
= (int8_t)*src
++;
75 if (x
< 0 || (x
= hexvals
[x
]) < 0
76 || y
< 0 || (y
= hexvals
[y
]) < 0)
79 while (((int8_t)*src
) < 0 ||
80 (*src
&& hexvals
[(uint8_t)*src
] < 0))
87 static void script_hexlify(char *dst
, const uint8_t *src
, size_t len
)
89 for (size_t i
= 0; i
< len
; ++i
) {
90 *dst
++ = hexdigits
[src
[i
] >> 4];
91 *dst
++ = hexdigits
[src
[i
] & 0x0f];
97 static void ipv6_to_env(const char *name
,
98 const struct in6_addr
*addr
, size_t cnt
)
100 size_t buf_len
= strlen(name
);
101 char *buf
= realloc(NULL
, cnt
* INET6_ADDRSTRLEN
+ buf_len
+ 2);
103 memcpy(buf
, name
, buf_len
);
104 buf
[buf_len
++] = '=';
106 for (size_t i
= 0; i
< cnt
; ++i
) {
107 inet_ntop(AF_INET6
, &addr
[i
], &buf
[buf_len
], INET6_ADDRSTRLEN
);
108 buf_len
+= strlen(&buf
[buf_len
]);
109 buf
[buf_len
++] = ' ';
112 if (buf
[buf_len
- 1] == ' ')
119 static void fqdn_to_env(const char *name
, const uint8_t *fqdn
, size_t len
)
121 size_t buf_len
= strlen(name
);
122 size_t buf_size
= len
+ buf_len
+ 2;
123 const uint8_t *fqdn_end
= fqdn
+ len
;
124 char *buf
= realloc(NULL
, len
+ buf_len
+ 2);
126 memcpy(buf
, name
, buf_len
);
127 buf
[buf_len
++] = '=';
129 while (fqdn
< fqdn_end
) {
130 int l
= dn_expand(fqdn
, fqdn_end
, fqdn
, &buf
[buf_len
], buf_size
- buf_len
);
134 buf_len
+= strlen(&buf
[buf_len
]);
135 buf
[buf_len
++] = ' ';
138 if (buf
[buf_len
- 1] == ' ')
145 static void bin_to_env(uint8_t *opts
, size_t len
)
147 uint8_t *oend
= opts
+ len
, *odata
;
148 uint16_t otype
, olen
;
150 dhcpv6_for_each_option(opts
, oend
, otype
, olen
, odata
) {
151 char *buf
= realloc(NULL
, 14 + (olen
* 2));
154 snprintf(buf
, 14, "OPTION_%hu=", otype
);
155 buf_len
+= strlen(buf
);
157 script_hexlify(&buf
[buf_len
], odata
, olen
);
169 static void entry_to_env(const char *name
, const void *data
, size_t len
, enum entry_type type
)
171 size_t buf_len
= strlen(name
);
172 const struct odhcp6c_entry
*e
= data
;
173 // Worst case: ENTRY_PREFIX with iaid != 1 and exclusion
174 const size_t max_entry_len
= (INET6_ADDRSTRLEN
-1 + 5 + 22 + 15 + 10 +
175 INET6_ADDRSTRLEN
-1 + 11 + 1);
176 char *buf
= realloc(NULL
, buf_len
+ 2 + (len
/ sizeof(*e
)) * max_entry_len
);
178 memcpy(buf
, name
, buf_len
);
179 buf
[buf_len
++] = '=';
181 for (size_t i
= 0; i
< len
/ sizeof(*e
); ++i
) {
183 * The only invalid entries allowed to be passed to the script are prefix entries.
184 * This will allow immediate removal of the old ipv6-prefix-assignment that might
185 * otherwise be kept for up to 2 hours (see L-13 requirement of RFC 7084).
187 if (!e
[i
].valid
&& type
!= ENTRY_PREFIX
)
190 inet_ntop(AF_INET6
, &e
[i
].target
, &buf
[buf_len
], INET6_ADDRSTRLEN
);
191 buf_len
+= strlen(&buf
[buf_len
]);
193 if (type
!= ENTRY_HOST
) {
194 snprintf(&buf
[buf_len
], 6, "/%"PRIu16
, e
[i
].length
);
195 buf_len
+= strlen(&buf
[buf_len
]);
197 if (type
== ENTRY_ROUTE
) {
198 buf
[buf_len
++] = ',';
200 if (!IN6_IS_ADDR_UNSPECIFIED(&e
[i
].router
)) {
201 inet_ntop(AF_INET6
, &e
[i
].router
, &buf
[buf_len
], INET6_ADDRSTRLEN
);
202 buf_len
+= strlen(&buf
[buf_len
]);
205 snprintf(&buf
[buf_len
], 23, ",%u,%u", e
[i
].valid
, e
[i
].priority
);
206 buf_len
+= strlen(&buf
[buf_len
]);
208 snprintf(&buf
[buf_len
], 23, ",%u,%u", e
[i
].preferred
, e
[i
].valid
);
209 buf_len
+= strlen(&buf
[buf_len
]);
212 if (type
== ENTRY_PREFIX
&& ntohl(e
[i
].iaid
) != 1) {
213 snprintf(&buf
[buf_len
], 16, ",class=%08x", ntohl(e
[i
].iaid
));
214 buf_len
+= strlen(&buf
[buf_len
]);
217 if (type
== ENTRY_PREFIX
&& e
[i
].priority
) {
218 // priority and router are abused for prefix exclusion
219 snprintf(&buf
[buf_len
], 11, ",excluded=");
220 buf_len
+= strlen(&buf
[buf_len
]);
221 inet_ntop(AF_INET6
, &e
[i
].router
, &buf
[buf_len
], INET6_ADDRSTRLEN
);
222 buf_len
+= strlen(&buf
[buf_len
]);
223 snprintf(&buf
[buf_len
], 12, "/%u", e
[i
].priority
);
224 buf_len
+= strlen(&buf
[buf_len
]);
228 buf
[buf_len
++] = ' ';
231 if (buf
[buf_len
- 1] == ' ')
238 static void search_to_env(const char *name
, const uint8_t *start
, size_t len
)
240 size_t buf_len
= strlen(name
);
241 char *buf
= realloc(NULL
, buf_len
+ 2 + len
);
242 char *c
= mempcpy(buf
, name
, buf_len
);
245 for (struct odhcp6c_entry
*e
= (struct odhcp6c_entry
*)start
;
246 (uint8_t*)e
< &start
[len
] &&
247 (uint8_t*)odhcp6c_next_entry(e
) <= &start
[len
];
248 e
= odhcp6c_next_entry(e
)) {
251 c
= mempcpy(c
, e
->auxtarget
, e
->auxlen
);
262 static void int_to_env(const char *name
, int value
)
264 size_t len
= 13 + strlen(name
);
265 char *buf
= realloc(NULL
, len
);
267 snprintf(buf
, len
, "%s=%d", name
, value
);
271 static void s46_to_env_portparams(const uint8_t *data
, size_t len
, FILE *fp
)
274 uint16_t otype
, olen
;
276 dhcpv6_for_each_option(data
, &data
[len
], otype
, olen
, odata
) {
277 if (otype
== DHCPV6_OPT_S46_PORTPARAMS
&&
278 olen
== sizeof(struct dhcpv6_s46_portparams
)) {
279 struct dhcpv6_s46_portparams
*params
= (void*)odata
;
280 fprintf(fp
, "offset=%d,psidlen=%d,psid=%d,",
281 params
->offset
, params
->psid_len
, ntohs(params
->psid
));
286 static void s46_to_env(enum odhcp6c_state state
, const uint8_t *data
, size_t len
)
288 const char *name
= (state
== STATE_S46_MAPE
) ? "MAPE" :
289 (state
== STATE_S46_MAPT
) ? "MAPT" : "LW4O6";
297 FILE *fp
= open_memstream(&str
, &strsize
);
301 const char *type
= (state
== STATE_S46_MAPE
) ? "map-e" :
302 (state
== STATE_S46_MAPT
) ? "map-t" : "lw4o6";
305 uint16_t otype
, olen
;
307 dhcpv6_for_each_option(data
, &data
[len
], otype
, olen
, odata
) {
308 struct dhcpv6_s46_rule
*rule
= (struct dhcpv6_s46_rule
*)odata
;
309 struct dhcpv6_s46_v4v6bind
*bind
= (struct dhcpv6_s46_v4v6bind
*)odata
;
311 if (state
!= STATE_S46_LW
&& otype
== DHCPV6_OPT_S46_RULE
&&
312 olen
>= sizeof(struct dhcpv6_s46_rule
)) {
313 char buf4
[INET_ADDRSTRLEN
];
314 char buf6
[INET6_ADDRSTRLEN
];
315 struct in6_addr in6
= IN6ADDR_ANY_INIT
;
317 size_t prefix6len
= rule
->prefix6_len
;
318 prefix6len
= (prefix6len
% 8 == 0) ? prefix6len
/ 8 : prefix6len
/ 8 + 1;
320 if (prefix6len
> sizeof(in6
) ||
321 olen
< sizeof(struct dhcpv6_s46_rule
) + prefix6len
)
324 memcpy(&in6
, rule
->ipv6_prefix
, prefix6len
);
326 inet_ntop(AF_INET
, &rule
->ipv4_prefix
, buf4
, sizeof(buf4
));
327 inet_ntop(AF_INET6
, &in6
, buf6
, sizeof(buf6
));
332 fprintf(fp
, "type=%s,ealen=%d,prefix4len=%d,prefix6len=%d,ipv4prefix=%s,ipv6prefix=%s,",
333 type
, rule
->ea_len
, rule
->prefix4_len
, rule
->prefix6_len
, buf4
, buf6
);
335 s46_to_env_portparams(&rule
->ipv6_prefix
[prefix6len
],
336 olen
- sizeof(*rule
) - prefix6len
, fp
);
338 dhcpv6_for_each_option(data
, &data
[len
], otype
, olen
, odata
) {
339 if (state
!= STATE_S46_MAPT
&& otype
== DHCPV6_OPT_S46_BR
&&
340 olen
== sizeof(struct in6_addr
)) {
341 inet_ntop(AF_INET6
, odata
, buf6
, sizeof(buf6
));
342 fprintf(fp
, "br=%s,", buf6
);
343 } else if (state
== STATE_S46_MAPT
&& otype
== DHCPV6_OPT_S46_DMR
&&
344 olen
>= sizeof(struct dhcpv6_s46_dmr
)) {
345 struct dhcpv6_s46_dmr
*dmr
= (struct dhcpv6_s46_dmr
*)odata
;
346 memset(&in6
, 0, sizeof(in6
));
347 size_t prefix6len
= dmr
->dmr_prefix6_len
;
348 prefix6len
= (prefix6len
% 8 == 0) ? prefix6len
/ 8 : prefix6len
/ 8 + 1;
350 if (prefix6len
> sizeof(in6
) ||
351 olen
< sizeof(struct dhcpv6_s46_dmr
) + prefix6len
)
354 memcpy(&in6
, dmr
->dmr_ipv6_prefix
, prefix6len
);
355 inet_ntop(AF_INET6
, &in6
, buf6
, sizeof(buf6
));
356 fprintf(fp
, "dmr=%s/%d,", buf6
, dmr
->dmr_prefix6_len
);
361 } else if (state
== STATE_S46_LW
&& otype
== DHCPV6_OPT_S46_V4V6BIND
&&
362 olen
>= sizeof(struct dhcpv6_s46_v4v6bind
)) {
363 char buf4
[INET_ADDRSTRLEN
];
364 char buf6
[INET6_ADDRSTRLEN
];
365 struct in6_addr in6
= IN6ADDR_ANY_INIT
;
367 size_t prefix6len
= bind
->bindprefix6_len
;
368 prefix6len
= (prefix6len
% 8 == 0) ? prefix6len
/ 8 : prefix6len
/ 8 + 1;
370 if (prefix6len
> sizeof(in6
) ||
371 olen
< sizeof(struct dhcpv6_s46_v4v6bind
) + prefix6len
)
374 memcpy(&in6
, bind
->bind_ipv6_prefix
, prefix6len
);
376 inet_ntop(AF_INET
, &bind
->ipv4_address
, buf4
, sizeof(buf4
));
377 inet_ntop(AF_INET6
, &in6
, buf6
, sizeof(buf6
));
379 fprintf(fp
, "type=%s,prefix4len=32,prefix6len=%d,ipv4prefix=%s,ipv6prefix=%s,",
380 type
, bind
->bindprefix6_len
, buf4
, buf6
);
382 s46_to_env_portparams(&bind
->bind_ipv6_prefix
[prefix6len
],
383 olen
- sizeof(*bind
) - prefix6len
, fp
);
385 dhcpv6_for_each_option(data
, &data
[len
], otype
, olen
, odata
) {
386 if (otype
== DHCPV6_OPT_S46_BR
&& olen
== sizeof(struct in6_addr
)) {
387 inet_ntop(AF_INET6
, odata
, buf6
, sizeof(buf6
));
388 fprintf(fp
, "br=%s,", buf6
);
400 void script_call(const char *status
, int delay
, bool resume
)
402 time_t now
= odhcp6c_get_milli_time() / 1000;
403 bool running_script
= false;
406 time_t diff
= now
- started
;
408 kill(running
, SIGTERM
);
415 running_script
= true;
418 if (resume
|| !running_script
|| !action
[0])
419 strncpy(action
, status
, sizeof(action
) - 1);
430 } else if (pid
== 0) {
431 size_t dns_len
, search_len
, custom_len
, sntp_ip_len
, ntp_ip_len
, ntp_dns_len
;
432 size_t sip_ip_len
, sip_fqdn_len
, aftr_name_len
, cer_len
, addr_len
;
433 size_t s46_mapt_len
, s46_mape_len
, s46_lw_len
, passthru_len
;
435 signal(SIGTERM
, SIG_DFL
);
438 odhcp6c_expire(false);
441 struct in6_addr
*addr
= odhcp6c_get_state(STATE_SERVER_ADDR
, &addr_len
);
442 struct in6_addr
*dns
= odhcp6c_get_state(STATE_DNS
, &dns_len
);
443 uint8_t *search
= odhcp6c_get_state(STATE_SEARCH
, &search_len
);
444 uint8_t *custom
= odhcp6c_get_state(STATE_CUSTOM_OPTS
, &custom_len
);
445 struct in6_addr
*sntp
= odhcp6c_get_state(STATE_SNTP_IP
, &sntp_ip_len
);
446 struct in6_addr
*ntp
= odhcp6c_get_state(STATE_NTP_IP
, &ntp_ip_len
);
447 uint8_t *ntp_dns
= odhcp6c_get_state(STATE_NTP_FQDN
, &ntp_dns_len
);
448 struct in6_addr
*sip
= odhcp6c_get_state(STATE_SIP_IP
, &sip_ip_len
);
449 uint8_t *sip_fqdn
= odhcp6c_get_state(STATE_SIP_FQDN
, &sip_fqdn_len
);
450 uint8_t *aftr_name
= odhcp6c_get_state(STATE_AFTR_NAME
, &aftr_name_len
);
451 struct in6_addr
*cer
= odhcp6c_get_state(STATE_CER
, &cer_len
);
452 uint8_t *s46_mapt
= odhcp6c_get_state(STATE_S46_MAPT
, &s46_mapt_len
);
453 uint8_t *s46_mape
= odhcp6c_get_state(STATE_S46_MAPE
, &s46_mape_len
);
454 uint8_t *s46_lw
= odhcp6c_get_state(STATE_S46_LW
, &s46_lw_len
);
455 uint8_t *passthru
= odhcp6c_get_state(STATE_PASSTHRU
, &passthru_len
);
457 size_t prefix_len
, address_len
, ra_pref_len
,
458 ra_route_len
, ra_dns_len
, ra_search_len
;
459 uint8_t *prefix
= odhcp6c_get_state(STATE_IA_PD
, &prefix_len
);
460 uint8_t *address
= odhcp6c_get_state(STATE_IA_NA
, &address_len
);
461 uint8_t *ra_pref
= odhcp6c_get_state(STATE_RA_PREFIX
, &ra_pref_len
);
462 uint8_t *ra_route
= odhcp6c_get_state(STATE_RA_ROUTE
, &ra_route_len
);
463 uint8_t *ra_dns
= odhcp6c_get_state(STATE_RA_DNS
, &ra_dns_len
);
464 uint8_t *ra_search
= odhcp6c_get_state(STATE_RA_SEARCH
, &ra_search_len
);
466 ipv6_to_env("SERVER", addr
, addr_len
/ sizeof(*addr
));
467 ipv6_to_env("RDNSS", dns
, dns_len
/ sizeof(*dns
));
468 ipv6_to_env("SNTP_IP", sntp
, sntp_ip_len
/ sizeof(*sntp
));
469 ipv6_to_env("NTP_IP", ntp
, ntp_ip_len
/ sizeof(*ntp
));
470 fqdn_to_env("NTP_FQDN", ntp_dns
, ntp_dns_len
);
471 ipv6_to_env("SIP_IP", sip
, sip_ip_len
/ sizeof(*sip
));
472 fqdn_to_env("DOMAINS", search
, search_len
);
473 fqdn_to_env("SIP_DOMAIN", sip_fqdn
, sip_fqdn_len
);
474 fqdn_to_env("AFTR", aftr_name
, aftr_name_len
);
475 ipv6_to_env("CER", cer
, cer_len
/ sizeof(*cer
));
476 s46_to_env(STATE_S46_MAPE
, s46_mape
, s46_mape_len
);
477 s46_to_env(STATE_S46_MAPT
, s46_mapt
, s46_mapt_len
);
478 s46_to_env(STATE_S46_LW
, s46_lw
, s46_lw_len
);
479 bin_to_env(custom
, custom_len
);
481 if (odhcp6c_is_bound()) {
482 entry_to_env("PREFIXES", prefix
, prefix_len
, ENTRY_PREFIX
);
483 entry_to_env("ADDRESSES", address
, address_len
, ENTRY_ADDRESS
);
486 entry_to_env("RA_ADDRESSES", ra_pref
, ra_pref_len
, ENTRY_ADDRESS
);
487 entry_to_env("RA_ROUTES", ra_route
, ra_route_len
, ENTRY_ROUTE
);
488 entry_to_env("RA_DNS", ra_dns
, ra_dns_len
, ENTRY_HOST
);
489 search_to_env("RA_DOMAINS", ra_search
, ra_search_len
);
491 int_to_env("RA_HOPLIMIT", ra_get_hoplimit());
492 int_to_env("RA_MTU", ra_get_mtu());
493 int_to_env("RA_REACHABLE", ra_get_reachable());
494 int_to_env("RA_RETRANSMIT", ra_get_retransmit());
496 char *buf
= malloc(10 + passthru_len
* 2);
497 strncpy(buf
, "PASSTHRU=", 10);
498 script_hexlify(&buf
[9], passthru
, passthru_len
);
501 execv(argv
[0], argv
);