git: 77f06c476c8c - main - rtadvd(8): support PREF64 (RFC 8781)

From: Warner Losh <imp_at_FreeBSD.org>
Date: Thu, 23 May 2024 20:41:46 UTC
The branch main has been updated by imp:

URL: https://cgit.FreeBSD.org/src/commit/?id=77f06c476c8cb8700bd1c154a4f4314d51286378

commit 77f06c476c8cb8700bd1c154a4f4314d51286378
Author:     Lexi Winter <lexi@le-Fay.ORG>
AuthorDate: 2024-04-26 21:41:37 +0000
Commit:     Warner Losh <imp@FreeBSD.org>
CommitDate: 2024-05-23 20:40:48 +0000

    rtadvd(8): support PREF64 (RFC 8781)
    
    PREF64 allows a router to advertise the network's NAT64 prefix, allowing
    clients to auto-configure CLAT.  This makes it possible to deploy
    IPv6-only or IPv6-mostly client access networks without the need for
    DNS64.
    
    Reviewed by: imp, glebius (prior suggetions done)
    Pull Request: https://github.com/freebsd/freebsd-src/pull/1206
---
 usr.sbin/rtadvd/config.c      | 68 +++++++++++++++++++++++++++++++++++++++++++
 usr.sbin/rtadvd/rtadvd.conf.5 | 18 ++++++++++++
 usr.sbin/rtadvd/rtadvd.h      | 11 +++++++
 3 files changed, 97 insertions(+)

diff --git a/usr.sbin/rtadvd/config.c b/usr.sbin/rtadvd/config.c
index bd990c58b36a..1b37d53c8b91 100644
--- a/usr.sbin/rtadvd/config.c
+++ b/usr.sbin/rtadvd/config.c
@@ -912,6 +912,58 @@ getconfig_free_dns:
 		}
 		free(dns);
 	}
+
+	/*
+	 * handle pref64
+	 */
+	rai->rai_pref64.p64_enabled = false;
+
+	if ((addr = (char *)agetstr("pref64", &bp))) {
+		if (inet_pton(AF_INET6, addr, &rai->rai_pref64.p64_prefix) != 1) {
+			syslog(LOG_ERR, "<%s> inet_pton failed for %s",
+			    __func__, addr);
+		} else {
+			rai->rai_pref64.p64_enabled = true;
+
+			switch (val64 = agetnum("pref64len")) {
+			case -1:
+			case 96:
+				rai->rai_pref64.p64_plc = 0;
+				break;
+			case 64:
+				rai->rai_pref64.p64_plc = 1;
+				break;
+			case 56:
+				rai->rai_pref64.p64_plc = 2;
+				break;
+			case 48:
+				rai->rai_pref64.p64_plc = 3;
+				break;
+			case 40:
+				rai->rai_pref64.p64_plc = 4;
+				break;
+			case 32:
+				rai->rai_pref64.p64_plc = 5;
+				break;
+			default:
+				syslog(LOG_ERR, "prefix length %" PRIi64
+				       "on %s is invalid; disabling PREF64",
+				       val64, ifi->ifi_ifname);
+				rai->rai_pref64.p64_enabled = 0;
+				break;
+			}
+
+			/* This logic is from RFC 8781 section 4.1. */
+			val64 = agetnum("pref64lifetime");
+			if (val64 == -1)
+				val64 = rai->rai_lifetime * 3;
+			if (val64 > 65528)
+				val64 = 65528;
+			val64 = (val64 + 7) / 8;
+			rai->rai_pref64.p64_sl = (uint16_t) (uint64_t) val64;
+		}
+	}
+
 	/* construct the sending packet */
 	make_packet(rai);
 
@@ -1334,6 +1386,7 @@ make_packet(struct rainfo *rai)
 	struct rdnss *rdn;
 	struct nd_opt_dnssl *ndopt_dnssl;
 	struct dnssl *dns;
+	struct nd_opt_pref64 *ndopt_pref64;
 	size_t len;
 	struct prefix *pfx;
 	struct ifinfo *ifi;
@@ -1355,6 +1408,8 @@ make_packet(struct rainfo *rai)
 		packlen += sizeof(struct nd_opt_prefix_info) * rai->rai_pfxs;
 	if (rai->rai_linkmtu)
 		packlen += sizeof(struct nd_opt_mtu);
+	if (rai->rai_pref64.p64_enabled)
+		packlen += sizeof(struct nd_opt_pref64);
 
 	TAILQ_FOREACH(rti, &rai->rai_route, rti_next)
 		packlen += sizeof(struct nd_opt_route_info) +
@@ -1435,6 +1490,19 @@ make_packet(struct rainfo *rai)
 		buf += sizeof(struct nd_opt_mtu);
 	}
 
+	if (rai->rai_pref64.p64_enabled) {
+		ndopt_pref64 = (struct nd_opt_pref64 *)buf;
+		ndopt_pref64->nd_opt_pref64_type = ND_OPT_PREF64;
+		ndopt_pref64->nd_opt_pref64_len = 2;
+		ndopt_pref64->nd_opt_pref64_sl_plc =
+			(htons(rai->rai_pref64.p64_sl << 3)) |
+			htons((rai->rai_pref64.p64_plc & 0x7));
+		memcpy(&ndopt_pref64->nd_opt_prefix[0],
+		       &rai->rai_pref64.p64_prefix,
+		       sizeof(ndopt_pref64->nd_opt_prefix));
+		buf += sizeof(struct nd_opt_pref64);
+	}
+
 	TAILQ_FOREACH(pfx, &rai->rai_prefix, pfx_next) {
 		uint32_t vltime, pltime;
 		struct timespec now;
diff --git a/usr.sbin/rtadvd/rtadvd.conf.5 b/usr.sbin/rtadvd/rtadvd.conf.5
index 6824d2a5578b..8158d09f99cf 100644
--- a/usr.sbin/rtadvd/rtadvd.conf.5
+++ b/usr.sbin/rtadvd/rtadvd.conf.5
@@ -428,6 +428,24 @@ DNS search list entries.
 The default value is 3/2 of the interval time.
 .El
 .Pp
+The following items are for PREF64 discovery
+.Pq RFC 8781 ,
+which will advertise the network's NAT64 prefix to clients.
+These items are optional.
+.Bl -tag -width indent
+.It Cm \&pref64
+(str) The prefix to advertise in the PREF64 option.
+.It Cm \&pref64len
+(num) The length of the PREF64 prefix.
+This must be 96, 64, 56, 48, 40, or 32.
+If not specified, the default is 96.
+.It Cm \&pref64lifetime
+(num) The prefix lifetime to advertise in the PREF64 option.
+This should be at least as long as the RA lifetime, but cannot be greater
+than 65528.
+If not specified, the default is the RA lifetime, or 65528, whichever is lower.
+.El
+.Pp
 You can also refer one line from another by using
 .Cm tc
 capability.
diff --git a/usr.sbin/rtadvd/rtadvd.h b/usr.sbin/rtadvd/rtadvd.h
index eb7746733c6e..597fb2f47f0d 100644
--- a/usr.sbin/rtadvd/rtadvd.h
+++ b/usr.sbin/rtadvd/rtadvd.h
@@ -32,6 +32,8 @@
  * SUCH DAMAGE.
  */
 
+#include <stdbool.h>
+
 #define	ELM_MALLOC(p,error_action)					\
 	do {								\
 		p = malloc(sizeof(*p));					\
@@ -148,6 +150,14 @@ struct rdnss {
 	uint32_t rd_ltime;	/* number of seconds valid */
 };
 
+struct pref64 {
+	TAILQ_ENTRY(pref64) p64_next;
+	bool		p64_enabled;
+	uint16_t	p64_plc;	/* prefix length code */
+	uint16_t	p64_sl;		/* scaled lifetime */
+	struct in6_addr	p64_prefix;
+};
+
 /*
  * The maximum length of a domain name in a DNS search list is calculated
  * by a domain name + length fields per 63 octets + a zero octet at
@@ -217,6 +227,7 @@ struct	rainfo {
 	/* actual RA packet data and its length */
 	size_t	rai_ra_datalen;
 	char	*rai_ra_data;
+	struct pref64 rai_pref64;	/* PREF64 option */
 
 	/* info about soliciter */
 	TAILQ_HEAD(, soliciter) rai_soliciter;	/* recent solication source */