git: 0951901814d1 - main - rtadvd: add multi pref64 support

From: Pouria Mousavizadeh Tehrani <pouria_at_FreeBSD.org>
Date: Fri, 06 Mar 2026 12:36:47 UTC
The branch main has been updated by pouria:

URL: https://cgit.FreeBSD.org/src/commit/?id=0951901814d1def721ac9a4fc3657af5c9694228

commit 0951901814d1def721ac9a4fc3657af5c9694228
Author:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
AuthorDate: 2026-03-06 11:48:23 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-03-06 12:35:09 +0000

    rtadvd: add multi pref64 support
    
    Add support for multi pref64 in rtadvd and rtadvctl
    
    Reviewed By: zlei, bz
    Differential Revision: https://reviews.freebsd.org/D54636
---
 usr.sbin/rtadvctl/rtadvctl.c     |  46 ++++++++++++++
 usr.sbin/rtadvd/config.c         | 130 ++++++++++++++++++++++-----------------
 usr.sbin/rtadvd/config.h         |   1 +
 usr.sbin/rtadvd/control_server.c |  56 +++++++++++++++++
 usr.sbin/rtadvd/rtadvd.c         |   8 ++-
 usr.sbin/rtadvd/rtadvd.conf.5    |  23 ++++++-
 usr.sbin/rtadvd/rtadvd.h         |   3 +-
 7 files changed, 204 insertions(+), 63 deletions(-)

diff --git a/usr.sbin/rtadvctl/rtadvctl.c b/usr.sbin/rtadvctl/rtadvctl.c
index 9fdb643cef10..201993ab06df 100644
--- a/usr.sbin/rtadvctl/rtadvctl.c
+++ b/usr.sbin/rtadvctl/rtadvctl.c
@@ -89,6 +89,7 @@ static int	action_show_prefix(struct prefix *);
 static int	action_show_rtinfo(struct rtinfo *);
 static int	action_show_rdnss(void *);
 static int	action_show_dnssl(void *);
+static void	action_show_pref64(void *);
 
 static int	csock_client_open(struct sockinfo *);
 static size_t	dname_labeldec(char *, size_t, const char *);
@@ -414,6 +415,7 @@ action_show(int argc, char **argv)
 	char argv_ifi_ra_timer[IFNAMSIZ + sizeof(":ifi_ra_timer=")];
 	char argv_rdnss[IFNAMSIZ + sizeof(":rdnss=")];
 	char argv_dnssl[IFNAMSIZ + sizeof(":dnssl=")];
+	char argv_pref64[IFNAMSIZ + sizeof(":pref64=")];
 	char ssbuf[SSBUFLEN];
 
 	struct timespec now, ts0, ts;
@@ -691,6 +693,21 @@ action_show(int argc, char **argv)
 			action_show_dnssl(cp.cp_val);
 		}
 
+		/* PREF64 information */
+		sprintf(argv_pref64, "%s:pref64=", ifi->ifi_ifname);
+		action_argv = argv_pref64;
+
+		error = action_propget(action_argv, &cp);
+		if (error)
+			continue;
+
+		len = *((uint16_t *)cp.cp_val);
+
+		if (len > 0) {
+			printf("\tPREF64:\n");
+			action_show_pref64(cp.cp_val);
+		}
+
 		if (vflag < LOG_NOTICE)
 			continue;
 
@@ -896,6 +913,35 @@ action_show_dnssl(void *msg)
 	return (0);
 }
 
+static void
+action_show_pref64(void *msg)
+{
+	struct pref64 *prf64;
+	uint16_t *prf64_cnt;
+	char ntopbuf[INET6_ADDRSTRLEN];
+	char ssbuf[SSBUFLEN];
+	char *p;
+	int i;
+	uint16_t prf64len;
+
+	p = msg;
+	prf64_cnt = (uint16_t *)p;
+	p += sizeof(*prf64_cnt);
+
+	for (i = 0; i < *prf64_cnt; i++) {
+		prf64 = (struct pref64 *)p;
+
+		/* RFC 8781 Section 4: Map PLC values to prefix lengths */
+		prf64len = (prf64->p64_plc == 0) ? 96 : 72 - (8 * prf64->p64_plc);
+		printf("\t  %s/%d (ltime: %s)\n",
+		    inet_ntop(AF_INET6, &prf64->p64_prefix,
+			ntopbuf, sizeof(ntopbuf)),
+		    prf64len, sec2str(prf64->p64_sl, ssbuf));
+
+		p += sizeof(*prf64);
+	}
+}
+
 /* Decode domain name label encoding in RFC 1035 Section 3.1 */
 static size_t
 dname_labeldec(char *dst, size_t dlen, const char *src)
diff --git a/usr.sbin/rtadvd/config.c b/usr.sbin/rtadvd/config.c
index 1b37d53c8b91..83b2efb68303 100644
--- a/usr.sbin/rtadvd/config.c
+++ b/usr.sbin/rtadvd/config.c
@@ -291,6 +291,7 @@ rm_rainfo(struct rainfo *rai)
 	struct rdnss *rdn;
 	struct rdnss_addr *rdna;
 	struct dnssl *dns;
+	struct pref64 *prf64;
 	struct rtinfo *rti;
 
 	syslog(LOG_DEBUG, "<%s>: enter",  __func__);
@@ -325,6 +326,10 @@ rm_rainfo(struct rainfo *rai)
 		TAILQ_REMOVE(&rai->rai_route, rti, rti_next);
 		free(rti);
 	}
+	while ((prf64 = TAILQ_FIRST(&rai->rai_pref64)) != NULL) {
+		TAILQ_REMOVE(&rai->rai_pref64, prf64, p64_next);
+		free(prf64);
+	}
 	free(rai);
 	syslog(LOG_DEBUG, "<%s>: leave",  __func__);
 
@@ -369,6 +374,7 @@ getconfig(struct ifinfo *ifi)
 	TAILQ_INIT(&rai->rai_route);
 	TAILQ_INIT(&rai->rai_rdnss);
 	TAILQ_INIT(&rai->rai_dnssl);
+	TAILQ_INIT(&rai->rai_pref64);
 	TAILQ_INIT(&rai->rai_soliciter);
 	rai->rai_ifinfo = ifi;
 
@@ -916,52 +922,62 @@ getconfig_free_dns:
 	/*
 	 * handle pref64
 	 */
-	rai->rai_pref64.p64_enabled = false;
+	for (i = -1; i < MAXPREF64 ; i++) {
+		struct pref64 *prf64;
+
+		makeentry(entbuf, sizeof(entbuf), i, "pref64");
+		addr = (char *)agetstr(entbuf, &bp);
+		if (addr == NULL)
+			continue;
+		ELM_MALLOC(prf64, exit(1));
 
-	if ((addr = (char *)agetstr("pref64", &bp))) {
-		if (inet_pton(AF_INET6, addr, &rai->rai_pref64.p64_prefix) != 1) {
+		if (inet_pton(AF_INET6, addr, &prf64->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;
-			}
+			goto getconfig_free_prf64;
+		}
 
-			/* 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;
+		makeentry(entbuf, sizeof(entbuf), i, "pref64len");
+		MAYHAVE(val64, entbuf, 96);
+		switch (val64) {
+		case 96:
+			prf64->p64_plc = 0;
+			break;
+		case 64:
+			prf64->p64_plc = 1;
+			break;
+		case 56:
+			prf64->p64_plc = 2;
+			break;
+		case 48:
+			prf64->p64_plc = 3;
+			break;
+		case 40:
+			prf64->p64_plc = 4;
+			break;
+		case 32:
+			prf64->p64_plc = 5;
+			break;
+		default:
+			syslog(LOG_ERR, "PREF64 prefix length %" PRIi64
+			       "on %s is invalid; skipping",
+			       val64, ifi->ifi_ifname);
+			goto getconfig_free_prf64;
 		}
+
+		makeentry(entbuf, sizeof(entbuf), i, "pref64lifetime");
+		MAYHAVE(val64, entbuf, (rai->rai_lifetime * 3));
+		/* This logic is from RFC 8781 section 4.1. */
+		if (val64 > 65528)
+			val64 = 65528;
+		val64 = (val64 + 7) / 8;
+		prf64->p64_sl = (uint16_t)val64;
+
+		/* link into chain */
+		TAILQ_INSERT_TAIL(&rai->rai_pref64, prf64, p64_next);
+		continue;
+getconfig_free_prf64:
+		free(prf64);
 	}
 
 	/* construct the sending packet */
@@ -1386,6 +1402,7 @@ make_packet(struct rainfo *rai)
 	struct rdnss *rdn;
 	struct nd_opt_dnssl *ndopt_dnssl;
 	struct dnssl *dns;
+	struct pref64 *prf64;
 	struct nd_opt_pref64 *ndopt_pref64;
 	size_t len;
 	struct prefix *pfx;
@@ -1408,8 +1425,6 @@ 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) +
@@ -1436,6 +1451,9 @@ make_packet(struct rainfo *rai)
 
 		packlen += len;
 	}
+	TAILQ_FOREACH(prf64, &rai->rai_pref64, p64_next)
+		packlen += sizeof(struct nd_opt_pref64);
+
 	/* allocate memory for the packet */
 	if ((buf = malloc(packlen)) == NULL) {
 		syslog(LOG_ERR,
@@ -1490,19 +1508,6 @@ 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;
@@ -1616,4 +1621,17 @@ make_packet(struct rainfo *rai)
 		syslog(LOG_DEBUG, "<%s>: nd_opt_dnssl_len = %d", __func__,
 		    ndopt_dnssl->nd_opt_dnssl_len);
 	}
+
+	TAILQ_FOREACH(prf64, &rai->rai_pref64, p64_next) {
+		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(prf64->p64_sl << 3)) |
+			htons((prf64->p64_plc & 0x7));
+		memcpy(&ndopt_pref64->nd_opt_prefix[0],
+		       &prf64->p64_prefix,
+		       sizeof(ndopt_pref64->nd_opt_prefix));
+		buf += sizeof(struct nd_opt_pref64);
+	}
 }
diff --git a/usr.sbin/rtadvd/config.h b/usr.sbin/rtadvd/config.h
index cfea1821ca5e..d795aab066cd 100644
--- a/usr.sbin/rtadvd/config.h
+++ b/usr.sbin/rtadvd/config.h
@@ -52,3 +52,4 @@ extern void get_prefix(struct rainfo *);
 #define MAXROUTE	100
 #define MAXRDNSSENT	100
 #define MAXDNSSLENT	100
+#define MAXPREF64	100
diff --git a/usr.sbin/rtadvd/control_server.c b/usr.sbin/rtadvd/control_server.c
index 60fdc5ca2ec0..e38045b0d574 100644
--- a/usr.sbin/rtadvd/control_server.c
+++ b/usr.sbin/rtadvd/control_server.c
@@ -80,6 +80,7 @@ static int cm_getprop_rai(struct ctrl_msg_pl *);
 static int cm_getprop_pfx(struct ctrl_msg_pl *);
 static int cm_getprop_rdnss(struct ctrl_msg_pl *);
 static int cm_getprop_dnssl(struct ctrl_msg_pl *);
+static int cm_getprop_pref64(struct ctrl_msg_pl *);
 static int cm_getprop_rti(struct ctrl_msg_pl *);
 
 static int cm_setprop_reload(struct ctrl_msg_pl *);
@@ -101,6 +102,7 @@ static struct dispatch_table {
 	DEF_PL_HANDLER(pfx),
 	DEF_PL_HANDLER(rdnss),
 	DEF_PL_HANDLER(dnssl),
+	DEF_PL_HANDLER(pref64),
 };
 
 static int
@@ -516,6 +518,60 @@ cm_getprop_dnssl(struct ctrl_msg_pl *cp)
 	return (0);
 }
 
+static int
+cm_getprop_pref64(struct ctrl_msg_pl *cp)
+{
+	struct ifinfo *ifi;
+	struct rainfo *rai;
+	struct pref64 *prf64;
+	char *p;
+	size_t len;
+	uint16_t *prf64_cnt;
+
+	syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+	len = 0;
+	TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+		if (strcmp(cp->cp_ifname, ifi->ifi_ifname) == 0)
+			break;
+	}
+	if (ifi == NULL) {
+		syslog(LOG_ERR, "<%s> %s not found", __func__,
+		    cp->cp_ifname);
+		return (1);
+	}
+	if (ifi->ifi_rainfo == NULL) {
+		syslog(LOG_ERR, "<%s> %s has no rainfo", __func__,
+		    cp->cp_ifname);
+		return (1);
+	}
+	rai = ifi->ifi_rainfo;
+
+	len = sizeof(*prf64_cnt);
+	TAILQ_FOREACH(prf64, &rai->rai_pref64, p64_next)
+		len += sizeof(*prf64);
+
+	syslog(LOG_DEBUG, "<%s> len = %zu", __func__, len);
+
+	p = malloc(len);
+	if (p == NULL)
+		exit(1);
+	memset(p, 0, len);
+	cp->cp_val = p;
+
+	prf64_cnt = (uint16_t *)cp->cp_val;
+	p += sizeof(*prf64_cnt);
+	TAILQ_FOREACH(prf64, &rai->rai_pref64, p64_next) {
+		(*prf64_cnt)++;
+		memcpy(p, prf64, sizeof(*prf64));
+		p += sizeof(*prf64);
+	}
+	cp->cp_val_len = p - cp->cp_val;
+
+	return (0);
+}
+
+
 int
 cm_getprop(struct ctrl_msg_pl *cp)
 {
diff --git a/usr.sbin/rtadvd/rtadvd.c b/usr.sbin/rtadvd/rtadvd.c
index fa5640afa96c..1eb8f12a7338 100644
--- a/usr.sbin/rtadvd/rtadvd.c
+++ b/usr.sbin/rtadvd/rtadvd.c
@@ -137,6 +137,7 @@ union nd_opt {
 #define NDOPT_FLAG_MTU		(1 << 4)
 #define NDOPT_FLAG_RDNSS	(1 << 5)
 #define NDOPT_FLAG_DNSSL	(1 << 6)
+#define NDOPT_FLAG_PREF64	(1 << 7)
 
 static uint32_t ndopt_flags[] = {
 	[ND_OPT_SOURCE_LINKADDR]	= NDOPT_FLAG_SRCLINKADDR,
@@ -146,6 +147,7 @@ static uint32_t ndopt_flags[] = {
 	[ND_OPT_MTU]			= NDOPT_FLAG_MTU,
 	[ND_OPT_RDNSS]			= NDOPT_FLAG_RDNSS,
 	[ND_OPT_DNSSL]			= NDOPT_FLAG_DNSSL,
+	[ND_OPT_PREF64]			= NDOPT_FLAG_PREF64,
 };
 
 static void	rtadvd_shutdown(void);
@@ -1083,7 +1085,7 @@ ra_input(int len, struct nd_router_advert *nra,
 	error = nd6_options((struct nd_opt_hdr *)(nra + 1),
 	    len - sizeof(struct nd_router_advert), &ndopts,
 	    NDOPT_FLAG_SRCLINKADDR | NDOPT_FLAG_PREFIXINFO | NDOPT_FLAG_MTU |
-	    NDOPT_FLAG_RDNSS | NDOPT_FLAG_DNSSL);
+	    NDOPT_FLAG_RDNSS | NDOPT_FLAG_DNSSL | NDOPT_FLAG_PREF64);
 	if (error) {
 		syslog(LOG_INFO,
 		    "<%s> ND option check failed for an RA from %s on %s",
@@ -1428,7 +1430,8 @@ nd6_options(struct nd_opt_hdr *hdr, int limit,
 
 		if (hdr->nd_opt_type > ND_OPT_MTU &&
 		    hdr->nd_opt_type != ND_OPT_RDNSS &&
-		    hdr->nd_opt_type != ND_OPT_DNSSL) {
+		    hdr->nd_opt_type != ND_OPT_DNSSL &&
+		    hdr->nd_opt_type != ND_OPT_PREF64) {
 			syslog(LOG_INFO, "<%s> unknown ND option(type %d)",
 			    __func__, hdr->nd_opt_type);
 			continue;
@@ -1473,6 +1476,7 @@ skip:
 		case ND_OPT_REDIRECTED_HEADER:
 		case ND_OPT_RDNSS:
 		case ND_OPT_DNSSL:
+		case ND_OPT_PREF64:
 			break;	/* we don't care about these options */
 		case ND_OPT_SOURCE_LINKADDR:
 		case ND_OPT_MTU:
diff --git a/usr.sbin/rtadvd/rtadvd.conf.5 b/usr.sbin/rtadvd/rtadvd.conf.5
index 8158d09f99cf..5af4865885c4 100644
--- a/usr.sbin/rtadvd/rtadvd.conf.5
+++ b/usr.sbin/rtadvd/rtadvd.conf.5
@@ -27,7 +27,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd June 4, 2011
+.Dd January 14, 2026
 .Dt RTADVD.CONF 5
 .Os
 .Sh NAME
@@ -435,6 +435,21 @@ These items are optional.
 .Bl -tag -width indent
 .It Cm \&pref64
 (str) The prefix to advertise in the PREF64 option.
+Multiple PREF64 prefixes can be specified by seperating entries using
+.Cm pref64 ,
+.Cm pref640 ,
+.Cm pref641 ,
+.Cm pref642 ...
+options with corresponding
+.Cm pref64len ,
+.Cm pref64len0 ,
+.Cm pref64len1 ,
+.Cm pref64len2 ...
+entries.
+This is also true for the
+.Cm pref64lifetime
+option.
+Note that the maximum number of prefixes depends on the receiver side.
 .It Cm \&pref64len
 (num) The length of the PREF64 prefix.
 This must be 96, 64, 56, 48, 40, or 32.
@@ -484,13 +499,15 @@ ef0:\\
 .Pp
 The following example configures the
 .Li wlan0
-interface and adds two DNS servers and a DNS domain search options
+interface and adds two DNS servers, a DNS domain search,
+and a PREF64 prefix,
 using the default option lifetime values.
 .Bd -literal -offset indent
 wlan0:\\
 	:addr="2001:db8:ffff:1000::":prefixlen#64:\\
 	:rdnss="2001:db8:ffff::10,2001:db8:ffff::2:43":\\
-	:dnssl="example.com":
+	:dnssl="example.com":\\
+	:pref64="64:ff9b::":
 .Ed
 .Pp
 The following example presents the default values in an explicit manner.
diff --git a/usr.sbin/rtadvd/rtadvd.h b/usr.sbin/rtadvd/rtadvd.h
index 597fb2f47f0d..5ecfd1b56423 100644
--- a/usr.sbin/rtadvd/rtadvd.h
+++ b/usr.sbin/rtadvd/rtadvd.h
@@ -152,7 +152,6 @@ struct rdnss {
 
 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;
@@ -227,7 +226,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 */
+	TAILQ_HEAD(, pref64) rai_pref64; /* PREF64 option */
 
 	/* info about soliciter */
 	TAILQ_HEAD(, soliciter) rai_soliciter;	/* recent solication source */