* started The DHCPv6 client has been started
* bound A suitable server was found and addresses or prefixes acquired
* informed A stateless information request returned updated information
-* timeout The DHCPv6 operation did not succeed within the defined time
* updated Updated information was received from the DHCPv6 server
+* ra-updated Updated information was received from via Router Advertisement
* rebound The DHCPv6 client switched to another server
* unbound The DHCPv6 client lost all DHCPv6 servers and will restart
* stopped The DHCPv6 client has been stopped
* OPTION_<num> Custom option received as base-16
* PREFIXES A space-separated list of prefixes currently assigned
Format: <prefix>/<length>,preferred,valid
+* ADDRESSES A space-separated list of addresses currently assigned
+ Format: <address>/<length>,preferred,valid
+* RA_ADDRESSES A space-separated list of addresses from RA-prefixes
+ Format: <address>/<length>,preferred,valid
+* RA_ROUTES A space-separated list of routes from the RA
+ Format: <address>/<length>@gateway,preferred,valid,metric
+* RA_DNS A space-separated list of recursive DNS servers from the RA
static volatile int do_signal = 0;
static int urandom_fd = -1;
-static bool bound = false;
+static bool bound = false, allow_slaac_only = false;
int main(_unused int argc, char* const argv[])
bool help = false, daemonize = false;
int c, request_pd = 0;
- while ((c = getopt(argc, argv, "N:P:c:r:s:hdp:")) != -1) {
+ while ((c = getopt(argc, argv, "SN:P:c:r:s:hdp:")) != -1) {
switch (c) {
+ case 'S':
+ allow_slaac_only = true;
+ break;
+
case 'N':
if (!strcmp(optarg, "force"))
ia_na_mode = IA_MODE_FORCE;
const char buf[] =
"Usage: odhcp6c [options] <interface>\n"
"\nFeature options:\n"
+ " -S Allow SLAAC-only assignment\n"
" -N <mode> Mode for requesting addresses [try|force|none]\n"
" -P <length> Request IPv6-Prefix (0 = auto)\n"
" -c <clientid> Override client-ID (base-16 encoded)\n"
do_signal = 0;
bool updated = ra_process();
updated |= ra_rtnl_process();
- if (updated && bound) {
+ if (updated && (bound || allow_slaac_only)) {
odhcp6c_expire();
script_call("ra-updated");
}
}
-void odhcp6c_update_entry_safe(enum odhcp6c_state state, const struct odhcp6c_entry *new, uint32_t safe)
+void odhcp6c_update_entry_safe(enum odhcp6c_state state, struct odhcp6c_entry *new, uint32_t safe)
{
size_t len;
struct odhcp6c_entry *x = odhcp6c_find_entry(state, new);
}
-void odhcp6c_update_entry(enum odhcp6c_state state, const struct odhcp6c_entry *new)
+void odhcp6c_update_entry(enum odhcp6c_state state, struct odhcp6c_entry *new)
{
odhcp6c_update_entry_safe(state, new, 0);
}
odhcp6c_expire_list(STATE_RA_PREFIX, elapsed);
odhcp6c_expire_list(STATE_RA_ROUTE, elapsed);
+ odhcp6c_expire_list(STATE_RA_DNS, elapsed);
odhcp6c_expire_list(STATE_IA_NA, elapsed);
odhcp6c_expire_list(STATE_IA_PD, elapsed);
}
STATE_SIP_FQDN,
STATE_RA_ROUTE,
STATE_RA_PREFIX,
+ STATE_RA_DNS,
_STATE_MAX
};
// Entry manipulation
struct odhcp6c_entry* odhcp6c_find_entry(enum odhcp6c_state state, const struct odhcp6c_entry *new);
-void odhcp6c_update_entry(enum odhcp6c_state state, const struct odhcp6c_entry *new);
-void odhcp6c_update_entry_safe(enum odhcp6c_state state, const struct odhcp6c_entry *new, uint32_t safe);
+void odhcp6c_update_entry(enum odhcp6c_state state, struct odhcp6c_entry *new);
+void odhcp6c_update_entry_safe(enum odhcp6c_state state, struct odhcp6c_entry *new, uint32_t safe);
void odhcp6c_expire(void);
}
found = true;
+ uint32_t router_valid = ntohs(adv->nd_ra_router_lifetime);
// Parse default route
entry.router = from.sin6_addr;
entry.priority = pref_to_priority(adv->nd_ra_flags_reserved);
if (entry.priority < 0)
entry.priority = pref_to_priority(0);
- entry.valid = ntohs(adv->nd_ra_router_lifetime);
+ entry.valid = router_valid;
entry.preferred = entry.valid;
odhcp6c_update_entry(STATE_RA_ROUTE, &entry);
if (adv->nd_ra_retransmit)
update_proc("neigh", "retrans_time_ms", ntohl(adv->nd_ra_retransmit));
+
// Evaluate options
struct icmpv6_opt *opt;
icmpv6_for_each_option(opt, &adv[1], &buf[len]) {
entry.target.s6_addr32[3] = lladdr.s6_addr32[3];
odhcp6c_update_entry_safe(STATE_RA_PREFIX, &entry, 7200);
- }
+ } else if (opt->type == ND_OPT_RECURSIVE_DNS && opt->len > 2) {
+ entry.router = from.sin6_addr;
+ entry.priority = 0;
+ entry.length = 128;
+ entry.valid = ntohl(*((uint32_t*)&opt->data[2]));
+ entry.preferred = 0;
+ for (ssize_t i = 0; i < (opt->len - 1) / 2; ++i) {
+ memcpy(&entry.target, &opt->data[6 + i * sizeof(entry.target)],
+ sizeof(entry.target));
+ odhcp6c_update_entry(STATE_RA_DNS, &entry);
+ }
+ }
}
+
+ size_t ra_dns_len;
+ struct odhcp6c_entry *entry = odhcp6c_get_state(STATE_RA_DNS, &ra_dns_len);
+ for (size_t i = 0; i < len / sizeof(*entry); ++i)
+ if (IN6_ARE_ADDR_EQUAL(&entry[i].router, &from.sin6_addr) &&
+ entry[i].valid > router_valid)
+ entry[i].valid = router_valid;
}
+
+ odhcp6c_expire();
return found;
}
}
-static void entry_to_env(const char *name, const void *data, size_t len)
+static void entry_to_env(const char *name, const void *data, size_t len, bool host)
{
size_t buf_len = strlen(name);
const struct odhcp6c_entry *e = data;
for (size_t i = 0; i < len / sizeof(*e); ++i) {
inet_ntop(AF_INET6, &e[i].target, &buf[buf_len], INET6_ADDRSTRLEN);
buf_len += strlen(&buf[buf_len]);
- buf_len += snprintf(&buf[buf_len], 6, "/%hhu", e[i].length);
- if (!IN6_ARE_ADDR_EQUAL(&any, &e[i].router)) {
- buf[buf_len++] = '@';
- inet_ntop(AF_INET6, &e[i].router, &buf[buf_len], INET6_ADDRSTRLEN);
- buf_len += strlen(&buf[buf_len]);
+ if (!host) {
+ buf_len += snprintf(&buf[buf_len], 6, "/%hhu", e[i].length);
+ if (!IN6_ARE_ADDR_EQUAL(&any, &e[i].router)) {
+ buf[buf_len++] = '@';
+ inet_ntop(AF_INET6, &e[i].router, &buf[buf_len], INET6_ADDRSTRLEN);
+ buf_len += strlen(&buf[buf_len]);
+ }
+ buf_len += snprintf(&buf[buf_len], 24, ",%u,%u", e[i].preferred, e[i].valid);
+ if (e[i].priority)
+ buf_len += snprintf(&buf[buf_len], 12, ",%u", e[i].priority);
}
- buf_len += snprintf(&buf[buf_len], 24, ",%u,%u", e[i].preferred, e[i].valid);
- if (e[i].priority)
- buf_len += snprintf(&buf[buf_len], 12, ",%u", e[i].priority);
buf[buf_len++] = ' ';
}
struct in6_addr *sip = odhcp6c_get_state(STATE_SIP_IP, &sip_ip_len);
uint8_t *sip_fqdn = odhcp6c_get_state(STATE_SIP_FQDN, &sip_fqdn_len);
- size_t prefix_len, address_len, ra_pref_len, ra_route_len;
+ size_t prefix_len, address_len, ra_pref_len, ra_route_len, ra_dns_len;
uint8_t *prefix = odhcp6c_get_state(STATE_IA_PD, &prefix_len);
uint8_t *address = odhcp6c_get_state(STATE_IA_NA, &address_len);
uint8_t *ra_pref = odhcp6c_get_state(STATE_RA_PREFIX, &ra_pref_len);
uint8_t *ra_route = odhcp6c_get_state(STATE_RA_ROUTE, &ra_route_len);
+ uint8_t *ra_dns = odhcp6c_get_state(STATE_RA_DNS, &ra_dns_len);
// Don't set environment before forking, because env is leaky.
if (fork() == 0) {
fqdn_to_env("SNTP_FQDN", sntp_dns, sntp_dns_len);
fqdn_to_env("SIP_DOMAIN", sip_fqdn, sip_fqdn_len);
bin_to_env(custom, custom_len);
- entry_to_env("PREFIXES", prefix, prefix_len);
- entry_to_env("ADDRESSES", address, address_len);
- entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len);
- entry_to_env("RA_ROUTES", ra_route, ra_route_len);
+ entry_to_env("PREFIXES", prefix, prefix_len, false);
+ entry_to_env("ADDRESSES", address, address_len, false);
+ entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len, false);
+ entry_to_env("RA_ROUTES", ra_route, ra_route_len, false);
+ entry_to_env("RA_DNS", ra_dns, ra_dns_len, true);
argv[2] = (char*)status;
execv(argv[0], argv);