From: Steven Barth <steven@midlink.org>
Date: Wed, 30 Jan 2013 21:15:07 +0000 (+0100)
Subject: Fix and improve RA-handling code
X-Git-Tag: debian/1.1+git20160131-1~195
X-Git-Url: https://git.decadent.org.uk/gitweb/?a=commitdiff_plain;h=6bc31b22abc26b9ee31b789d56a8e2a07aa4c569;p=odhcp6c.git

Fix and improve RA-handling code
---

diff --git a/README b/README
index 421475f..f43c60f 100644
--- a/README
+++ b/README
@@ -39,8 +39,8 @@ States:
 * 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
@@ -56,6 +56,13 @@ Environment:
 * 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
 
 
 
diff --git a/src/odhcp6c.c b/src/odhcp6c.c
index e4da1ab..6a1d1b4 100644
--- a/src/odhcp6c.c
+++ b/src/odhcp6c.c
@@ -41,7 +41,7 @@ static size_t state_len[_STATE_MAX] = {0};
 
 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[])
@@ -59,8 +59,12 @@ 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;
@@ -284,6 +288,7 @@ static int usage(void)
 	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"
@@ -329,7 +334,7 @@ bool odhcp6c_signal_process(void)
 		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");
 		}
@@ -386,7 +391,7 @@ struct odhcp6c_entry* odhcp6c_find_entry(enum odhcp6c_state state, const struct
 }
 
 
-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);
@@ -406,7 +411,7 @@ void odhcp6c_update_entry_safe(enum odhcp6c_state state, const struct odhcp6c_en
 }
 
 
-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);
 }
@@ -443,6 +448,7 @@ void odhcp6c_expire(void)
 
 	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);
 }
diff --git a/src/odhcp6c.h b/src/odhcp6c.h
index 440381a..eae9b7a 100644
--- a/src/odhcp6c.h
+++ b/src/odhcp6c.h
@@ -164,6 +164,7 @@ enum odhcp6c_state {
 	STATE_SIP_FQDN,
 	STATE_RA_ROUTE,
 	STATE_RA_PREFIX,
+	STATE_RA_DNS,
 	_STATE_MAX
 };
 
@@ -224,7 +225,7 @@ void* odhcp6c_get_state(enum odhcp6c_state state, size_t *len);
 
 // 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);
diff --git a/src/ra.c b/src/ra.c
index 0d51832..f9fd573 100644
--- a/src/ra.c
+++ b/src/ra.c
@@ -193,13 +193,14 @@ bool ra_process(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);
 
@@ -210,6 +211,7 @@ bool ra_process(void)
 		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]) {
@@ -256,9 +258,29 @@ bool ra_process(void)
 				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;
 }
diff --git a/src/script.c b/src/script.c
index 0e01f3a..4eec04d 100644
--- a/src/script.c
+++ b/src/script.c
@@ -129,7 +129,7 @@ static void bin_to_env(uint8_t *opts, size_t len)
 }
 
 
-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;
@@ -141,15 +141,17 @@ static void entry_to_env(const char *name, const void *data, size_t len)
 	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++] = ' ';
 	}
 
@@ -172,11 +174,12 @@ void script_call(const char *status)
 	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) {
@@ -187,10 +190,11 @@ void script_call(const char *status)
 		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);