aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBen Hutchings <ben@decadent.org.uk>2023-06-05 05:22:07 +0200
committerBen Hutchings <ben@decadent.org.uk>2023-10-26 01:10:39 +0200
commit99fd4e4d82d60088ef512dcab6162fec83df8d60 (patch)
treee2c80e06cbef588ca4443f86a28ac1856aa6a953
parentcc79247bc1129db56ba50ccaf4267729a5b1bd76 (diff)
downloadklibc-ipv6.tar.gz
WIP: [klibc] ipconfig: Add support for RA-DNS alongside SLAACipv6
Add support for DNS options in Router Advertisements (RFC 8106). As with SLAAC, we wait for the kernel to receive this information and notify us through rtnetlink. These notifications are separate from new address notification, and there's unfortunately no way to know that an RA was received without DNS options. TODO: add sensible timeout TODO: should RA-DNS be optional?
-rw-r--r--usr/kinit/ipconfig/netdev.h3
-rw-r--r--usr/kinit/ipconfig/slaac_proto.c123
2 files changed, 115 insertions, 11 deletions
diff --git a/usr/kinit/ipconfig/netdev.h b/usr/kinit/ipconfig/netdev.h
index 726e44642e40cf..c33991567637e1 100644
--- a/usr/kinit/ipconfig/netdev.h
+++ b/usr/kinit/ipconfig/netdev.h
@@ -53,8 +53,9 @@ struct netdev {
uint8_t serverid_opt[4 + 2 + 128];
} dhcpv6;
- struct { /* SLAAC information */
+ struct { /* SLAAC/RA-DNS information */
bool have_addr;
+ bool have_dns;
} slaac;
int family; /* address family used */
diff --git a/usr/kinit/ipconfig/slaac_proto.c b/usr/kinit/ipconfig/slaac_proto.c
index e25fec9fd233b2..fb44ee36da3d5c 100644
--- a/usr/kinit/ipconfig/slaac_proto.c
+++ b/usr/kinit/ipconfig/slaac_proto.c
@@ -1,5 +1,5 @@
/*
- * SLAAC (RFC 4862)
+ * SLAAC (RFC 4862) and RA-DNS (RFC 8106)
*/
#include <stdlib.h>
#include <time.h>
@@ -11,12 +11,23 @@
#include "netdev.h"
#include "slaac_proto.h"
+#include "dns.h"
struct slaac_state {
struct mnl_socket *rtnl;
struct netdev *dev_list;
};
+#define ND_ROUTER_ADVERT 134
+
+/* Neighbour Discovery option header */
+struct nd_opt_hdr {
+ uint8_t nd_opt_type;
+ uint8_t nd_opt_len;
+};
+#define ND_OPT_RDNSS 25
+#define ND_OPT_DNSSL 31
+
static void slaac_close(struct slaac_state *slaac);
struct slaac_state *slaac_open(void)
@@ -35,7 +46,9 @@ struct slaac_state *slaac_open(void)
return NULL;
/* Receive notifications of new addresses */
- if (mnl_socket_bind(slaac->rtnl, RTMGRP_IPV6_IFADDR,
+ if (mnl_socket_bind(slaac->rtnl,
+ (1 << (RTNLGRP_IPV6_IFADDR - 1)) |
+ (1 << (RTNLGRP_ND_USEROPT - 1)),
MNL_SOCKET_AUTOPID) < 0)
goto err_close;
@@ -90,19 +103,15 @@ static int slaac_recv_attr_cb(const struct nlattr *attr, void *context)
return MNL_CB_OK;
}
-static int slaac_recv_cb(const struct nlmsghdr *nlh, void *context)
+static int
+slaac_handle_newaddr(const struct nlmsghdr *nlh, struct slaac_state *slaac)
{
struct nlattr *attrs[IFA_MAX + 1] = {};
struct ifaddrmsg *ifa = mnl_nlmsg_get_payload(nlh);
- struct slaac_state *slaac = context;
struct netdev *dev;
mnl_attr_parse(nlh, sizeof(*ifa), slaac_recv_attr_cb, attrs);
- /* Ignore deletions */
- if (nlh->nlmsg_type != RTM_NEWADDR)
- return MNL_CB_OK;
-
/* Ignore other devices and completed devices */
for (dev = slaac->dev_list;
dev && dev->ifindex != ifa->ifa_index;
@@ -122,8 +131,97 @@ static int slaac_recv_cb(const struct nlmsghdr *nlh, void *context)
sizeof(dev->ip_addr.v6));
dev->ip_prefix_len = ifa->ifa_prefixlen;
dev->slaac.have_addr = true;
+ }
+
+ return MNL_CB_OK;
+}
+
+static int
+slaac_handle_newnduseropt(const struct nlmsghdr *nlh, struct slaac_state *slaac)
+{
+ struct nduseroptmsg *nduseropt = mnl_nlmsg_get_payload(nlh);
+ struct netdev *dev;
+ const uint8_t *opts;
+ size_t len, off, opt_len;
+ const struct nd_opt_hdr *opt;
+
+ /* Ignore anything but RAs */
+ if (nduseropt->nduseropt_family != AF_INET6 ||
+ nduseropt->nduseropt_icmp_type != ND_ROUTER_ADVERT ||
+ nduseropt->nduseropt_icmp_code != 0)
+ return MNL_CB_OK;
+
+ /* Ignore other devices and completed devices */
+ for (dev = slaac->dev_list;
+ dev && dev->ifindex != nduseropt->nduseropt_ifindex;
+ dev = dev->next)
+ ;
+ if (!dev)
+ return MNL_CB_OK;
+
+ opts = (const uint8_t *)(nduseropt + 1);
+ len = nduseropt->nduseropt_opts_len;
+
+ for (off = 0; off + sizeof(*opt) <= len; off += opt_len) {
+ opt = (const struct nd_opt_hdr *)(opts + off);
+ opt_len = opt->nd_opt_len * 8;
+ if (opt_len == 0 || opt_len > len - off)
+ break;
+
+ switch (opt->nd_opt_type) {
+ case ND_OPT_RDNSS:
+ if (opt_len >= 8 + sizeof(struct in6_addr))
+ memcpy(&dev->ip_nameserver[0].v6,
+ opts + off + 8,
+ sizeof(struct in6_addr));
+ if (opt_len >= 8 + 2 * sizeof(struct in6_addr))
+ memcpy(&dev->ip_nameserver[1].v6,
+ opts + off + 8 + sizeof(struct in6_addr),
+ sizeof(struct in6_addr));
+ dev->slaac.have_dns = true;
+ break;
+
+ case ND_OPT_DNSSL: {
+ size_t list_len;
+
+ free(dev->domainsearch);
+ if (opt_len > 8) {
+ /*
+ * Trim zero-padding that would be
+ * decoded into spurious "." domains
+ */
+ list_len = opt_len - 8;
+ while (list_len >= 2 &&
+ opts[off + 8 + list_len - 1] == 0 &&
+ opts[off + 8 + list_len - 2] == 0)
+ --list_len;
+ dev->domainsearch = dns_list_decode(
+ opts + off + 8, list_len, NULL);
+ } else {
+ dev->domainsearch = NULL;
+ }
+
+ dev->slaac.have_dns = true;
+ break;
+ }
+ }
+ }
+
+ return MNL_CB_OK;
+}
+
+static int slaac_recv_cb(const struct nlmsghdr *nlh, void *context)
+{
+ struct slaac_state *state = context;
+
+ switch (nlh->nlmsg_type) {
+ case RTM_NEWADDR:
+ slaac_handle_newaddr(nlh, state);
+ break;
- /* TODO: get RDNSS information */
+ case RTM_NEWNDUSEROPT:
+ slaac_handle_newnduseropt(nlh, state);
+ break;
}
return MNL_CB_OK;
@@ -167,7 +265,12 @@ static int slaac_nl_receive(struct state *s)
if (s2->state != DEVST_SLAACDEV)
continue;
dev = s2->context;
- if (!dev->slaac.have_addr) {
+ /* TODO: should only wait a short time for
+ * RA-DNS after SLAAC has completed, since
+ * we won't receive any notification about
+ * RAs without the specific options
+ */
+ if (!dev->slaac.have_addr || !dev->slaac.have_dns) {
all_complete = false;
continue;
}