diff options
| author | Denys Fedoryshchenko <denys.f@collabora.com> | 2026-08-04 18:39:51 +0300 |
|---|---|---|
| committer | Denys Fedoryshchenko <denys.f@collabora.com> | 2026-08-12 10:42:35 +0300 |
| commit | 58ef850d1705d88f006d761d32c55bde5205a27d (patch) | |
| tree | 901034d82cd545fb8974e580cbec325576b1c2a9 /accel-pppd/radius | |
| parent | 4e36e08e90dbd3c27f566ca396f4284b77f1bf40 (diff) | |
| download | accel-ppp-58ef850d1705d88f006d761d32c55bde5205a27d.tar.gz accel-ppp-58ef850d1705d88f006d761d32c55bde5205a27d.zip | |
ipv6: assign DNS servers per session from RADIUS
The ipv6_nd and ipv6_dhcp modules could only advertise the DNS servers
configured in [ipv6-dns], the same set for every subscriber. RFC 6911
defines DNS-Server-IPv6-Address (attribute 169) for exactly this, and
the attribute was already in the shipped dictionary and in attr_defs.h;
nothing read it.
Give struct ap_session an ipv6_dns list, filled by the radius module
from that attribute, and have both modules advertise it when the session
has one: in the RDNSS option of the router advertisements, and in the
DNS_SERVERS option of DHCPv6 replies. Sessions without a list of their
own keep getting the configured servers, so nothing changes for anyone
not sending the attribute.
The selection is a single ipv6_dns_get() shared by both modules rather
than a copy in each: they already duplicate the whole [ipv6-dns] parser,
and two copies of a precedence rule are two chances to drift. It caps
what it returns, so neither the RDNSS option length (one byte, in units
of 8) nor the router advertisement buffer can be pushed around by what a
RADIUS server sends. The radius module caps at the same 3 servers as
[ipv6-dns] accepts and warns once when a reply carries more.
An Access-Accept which carries the attribute replaces the whole
previously assigned list rather than appending to it, so a re-authorized
session ends up with the servers of the latest reply and not with a
concatenation. One which does not carry it leaves the current list
alone, which is how the IPv4 MS-Primary-DNS-Server attribute already
behaves.
ipv6_dns_test.c covers the selection: assigned wins over configured,
empty list means "nothing assigned" rather than "no DNS", the cap holds
for both sources, and a caller with no room gets nothing rather than a
stomped buffer. Wired into the ASAN/UBSAN workflow.
Inspired by the per-session IPv6 DNS support in accel-ppp-ng (commit
2df6eb99), reimplemented against mainline's ap_session and ipdb types.
Diffstat (limited to 'accel-pppd/radius')
| -rw-r--r-- | accel-pppd/radius/radius.c | 50 | ||||
| -rw-r--r-- | accel-pppd/radius/radius_p.h | 1 |
2 files changed, 51 insertions, 0 deletions
diff --git a/accel-pppd/radius/radius.c b/accel-pppd/radius/radius.c index cb53f59a..fa77a916 100644 --- a/accel-pppd/radius/radius.c +++ b/accel-pppd/radius/radius.c @@ -488,12 +488,31 @@ err: return -1; } +/* + * Number of IPv6 DNS servers kept per session. Matches the number of dns= + * options the ipv6_nd and ipv6_dhcp modules accept in [ipv6-dns], and keeps + * the RDNSS option of a router advertisement to a sane size. + */ +#define MAX_DNS6_COUNT 3 + +static void free_ipv6_dns(struct radius_pd_t *rpd) +{ + struct ipv6db_addr_t *a; + + while (!list_empty(&rpd->ipv6_dns.addr_list)) { + a = list_entry(rpd->ipv6_dns.addr_list.next, typeof(*a), entry); + list_del(&a->entry); + _free(a); + } +} + int rad_proc_attrs(struct rad_req_t *req) { struct ev_wins_t wins = {}; struct ev_dns_t dns = {}; struct rad_attr_t *attr; struct ipv6db_addr_t *a; + int dns6_count = -1; int res = 0; struct radius_pd_t *rpd = req->rpd; @@ -602,6 +621,28 @@ int rad_proc_attrs(struct rad_req_t *req) a->addr = attr->val.ipv6prefix.prefix; list_add_tail(&a->entry, &rpd->ipv6_dp.prefix_list); break; + case DNS_Server_IPv6_Address: + if (dns6_count < 0) { + /* This reply carries a DNS server list of + its own, it replaces whatever a previous + one assigned */ + free_ipv6_dns(rpd); + dns6_count = 0; + } + if (dns6_count >= MAX_DNS6_COUNT) { + if (dns6_count == MAX_DNS6_COUNT) + log_ppp_warn("radius: ignoring DNS-Server-IPv6-Address" + " beyond the first %i\n", MAX_DNS6_COUNT); + dns6_count++; + break; + } + a = _malloc(sizeof(*a)); + memset(a, 0, sizeof(*a)); + a->prefix_len = 128; + a->addr = attr->val.ipv6addr; + list_add_tail(&a->entry, &rpd->ipv6_dns.addr_list); + dns6_count++; + break; case NAS_Port: rpd->ses->unit_idx = attr->val.integer; break; @@ -635,6 +676,11 @@ int rad_proc_attrs(struct rad_req_t *req) if (!rpd->ses->ipv6_dp && !list_empty(&rpd->ipv6_dp.prefix_list)) rpd->ses->ipv6_dp = &rpd->ipv6_dp; + /* Like the IPv4 DNS servers, absent attributes leave whatever a + previous reply assigned in place */ + if (!list_empty(&rpd->ipv6_dns.addr_list)) + rpd->ses->ipv6_dns = &rpd->ipv6_dns; + return res; } @@ -799,6 +845,7 @@ static void ses_starting(struct ap_session *ses) INIT_LIST_HEAD(&rpd->plugin_list); INIT_LIST_HEAD(&rpd->ipv6_addr.addr_list); INIT_LIST_HEAD(&rpd->ipv6_dp.prefix_list); + INIT_LIST_HEAD(&rpd->ipv6_dns.addr_list); rpd->ipv4_addr.owner = &ipdb; rpd->ipv6_addr.owner = &ipdb; @@ -981,6 +1028,9 @@ static void ses_finished(struct ap_session *ses) _free(a); } + ses->ipv6_dns = NULL; + free_ipv6_dns(rpd); + fr6 = rpd->fr6; while (fr6) { struct framed_ip6_route *next = fr6->next; diff --git a/accel-pppd/radius/radius_p.h b/accel-pppd/radius/radius_p.h index d3a72204..e4b84740 100644 --- a/accel-pppd/radius/radius_p.h +++ b/accel-pppd/radius/radius_p.h @@ -65,6 +65,7 @@ struct radius_pd_t { struct ipv4db_item_t ipv4_addr; struct ipv6db_item_t ipv6_addr; struct ipv6db_prefix_t ipv6_dp; + struct ipv6db_item_t ipv6_dns; int acct_interim_interval; int acct_interim_jitter; |
