summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Fedoryshchenko <denys.f@collabora.com>2026-08-27 09:16:31 +0300
committerDenys Fedoryshchenko <denys.f@collabora.com>2026-10-06 02:19:48 +0300
commit2823a07df0c88e9d98472b9248f2e9c3179f6920 (patch)
tree4dca27724df4224c23fde20ac7ae4502879f5dad
parentd8084108cc750962dafcd3716f5d243de2e56418 (diff)
downloadaccel-ppp-2823a07df0c88e9d98472b9248f2e9c3179f6920.tar.gz
accel-ppp-2823a07df0c88e9d98472b9248f2e9c3179f6920.zip
ipv6: remove obsolete three-server DNS limit
The legacy limit matches RFC 6106's recommendation that hosts treat three learned RDNSS addresses as sufficient. That was host-side repository guidance, not a limit in the RADIUS, DHCPv6, or RDNSS encodings. RFC 8106 obsoletes RFC 6106 and explicitly removes the recommendation to limit learned RDNSS addresses to three, leaving the number to local policy. It instead recommends the ability to store at least three addresses when DNS information comes from multiple sources. Store both configured and per-session servers in reusable contiguous arrays. Start with a four-address, 64-byte allocation, grow geometrically, retain capacity across reload or reauthorization, and free session storage only at teardown. Bound advertised lists by actual DHCPv6 and router-advertisement packet space. Warn when servers have to be dropped for lack of room, as the fixed limit used to, skip the DNS search list rather than write it past the end of a full advertisement, and drop the stale three-server wording from the [ipv6-dns] documentation. Link: https://github.com/accel-ppp/accel-ppp/commit/4f562467dbdf819395e138617c2a057e02595b9e#r197212083 Link: https://www.rfc-editor.org/rfc/rfc6106.html#section-5.3.1 Link: https://www.rfc-editor.org/rfc/rfc8106.html#section-5.3.1 Link: https://www.rfc-editor.org/rfc/rfc8106.html#appendix-A
-rw-r--r--.github/workflows/run-tests-asan-ubsan.yml3
-rw-r--r--accel-pppd/CMakeLists.txt2
-rw-r--r--accel-pppd/accel-ppp.conf.56
-rw-r--r--accel-pppd/include/ipv6_dns.h46
-rw-r--r--accel-pppd/ipv6/dhcpv6.c41
-rw-r--r--accel-pppd/ipv6/dhcpv6.h1
-rw-r--r--accel-pppd/ipv6/dhcpv6_packet.c27
-rw-r--r--accel-pppd/ipv6/ipv6_dns_test.c74
-rw-r--r--accel-pppd/ipv6/nd.c51
-rw-r--r--accel-pppd/ipv6_dns.c33
-rw-r--r--accel-pppd/radius/radius.c61
11 files changed, 199 insertions, 146 deletions
diff --git a/.github/workflows/run-tests-asan-ubsan.yml b/.github/workflows/run-tests-asan-ubsan.yml
index 90074463..0ee2c755 100644
--- a/.github/workflows/run-tests-asan-ubsan.yml
+++ b/.github/workflows/run-tests-asan-ubsan.yml
@@ -75,7 +75,8 @@ jobs:
gcc -O2 -Wall -D_GNU_SOURCE -DAP_SESSIONID_LEN=16 \
-fsanitize=${{ matrix.sanitizer }} -fno-sanitize-recover=all \
-I accel-pppd/include -I accel-pppd/triton -I build \
- -o /tmp/ipv6_dns_test accel-pppd/ipv6/ipv6_dns_test.c
+ -o /tmp/ipv6_dns_test \
+ accel-pppd/ipv6/ipv6_dns_test.c accel-pppd/ipv6_dns.c
/tmp/ipv6_dns_test
gcc -O2 -Wall -o /tmp/bitpool_test accel-pppd/extra/bitpool_test.c
/tmp/bitpool_test
diff --git a/accel-pppd/CMakeLists.txt b/accel-pppd/CMakeLists.txt
index 49c2ff61..c9283dfd 100644
--- a/accel-pppd/CMakeLists.txt
+++ b/accel-pppd/CMakeLists.txt
@@ -90,6 +90,7 @@ int main(void)
ADD_EXECUTABLE(accel-pppd
memdebug.c
+ ipv6_dns.c
session.c
session_backup.c
ifcfg.c
@@ -165,4 +166,3 @@ IF (NOT DEFINED CPACK_TYPE)
INSTALL(DIRECTORY DESTINATION "${CMAKE_INSTALL_LOCALSTATEDIR}/log/accel-ppp")
INSTALL(DIRECTORY DESTINATION "${CMAKE_INSTALL_LOCALSTATEDIR}/lib/accel-ppp")
ENDIF (NOT DEFINED CPACK_TYPE)
-
diff --git a/accel-pppd/accel-ppp.conf.5 b/accel-pppd/accel-ppp.conf.5
index 5ee7fd15..b6e8528c 100644
--- a/accel-pppd/accel-ppp.conf.5
+++ b/accel-pppd/accel-ppp.conf.5
@@ -673,14 +673,16 @@ Specifies primary NBNS to be sent to peer.
Specifies secondary NBNS to be sent to peer.
.SH [ipv6-dns]
These options apply to sessions which were not assigned DNS servers of their
-own. A RADIUS server may assign per session ones by returning up to 3
+own. A RADIUS server may assign per session ones by returning
DNS-Server-IPv6-Address attributes (RFC 6911) in the Access-Accept; those
replace, rather than extend, the servers configured here, for that session
only. Both the ipv6_nd module (RDNSS option of the router advertisements) and
the ipv6_dhcp module (DNS_SERVERS option) honour them.
.TP
.BI "dns=" IPv6_address
-Specifies IPv6 DNS to be sent to peer. You may specify up to 3 dns options.
+Specifies IPv6 DNS to be sent to peer. You may specify multiple dns options.
+Servers which do not fit into a router advertisement or a DHCPv6 reply are
+dropped from it and a warning is logged.
.TP
.BI "dnssl=" name
Specify DNS Search List. You may specify multiple dns and dnssl options.
diff --git a/accel-pppd/include/ipv6_dns.h b/accel-pppd/include/ipv6_dns.h
index b1d74708..e82812ce 100644
--- a/accel-pppd/include/ipv6_dns.h
+++ b/accel-pppd/include/ipv6_dns.h
@@ -3,18 +3,18 @@
#include <netinet/in.h>
-#include "list.h"
#include "ap_session.h"
-struct ipv6_dns_addr_t {
- struct list_head entry;
- struct in6_addr addr;
-};
+#define IPV6_DNS_INITIAL_CAPACITY 4
struct ipv6_dns_t {
- struct list_head addr_list;
+ struct in6_addr *addr;
+ unsigned int count;
+ unsigned int capacity;
};
+int ipv6_dns_reserve(struct ipv6_dns_t *dns, unsigned int count);
+
/*
* Pick the IPv6 DNS servers to advertise to a session.
*
@@ -23,34 +23,20 @@ struct ipv6_dns_t {
* precedence. Sessions without any fall back to the globally configured ones,
* which is what every session got before per session servers existed.
*
- * Up to 'max' addresses are written to 'dns', the number written is returned.
- * Callers advertise nothing when that is 0.
+ * The selected array is returned and its length is written to 'count'.
+ * Callers advertise nothing when the returned count is 0.
*/
-static inline int ipv6_dns_get(const struct ap_session *ses,
- const struct in6_addr *conf_dns, int conf_dns_count,
- struct in6_addr *dns, int max)
+static inline const struct in6_addr *ipv6_dns_get(const struct ap_session *ses,
+ const struct in6_addr *conf_dns,
+ int conf_dns_count, int *count)
{
- struct ipv6_dns_addr_t *a;
- int count = 0;
-
- if (ses && ses->ipv6_dns) {
- list_for_each_entry(a, &ses->ipv6_dns->addr_list, entry) {
- if (count == max)
- break;
- dns[count++] = a->addr;
- }
-
- /* An empty list means "nothing assigned", not "no DNS at all" */
- if (count)
- return count;
- }
-
- while (count < conf_dns_count && count < max) {
- dns[count] = conf_dns[count];
- count++;
+ if (ses && ses->ipv6_dns && ses->ipv6_dns->count) {
+ *count = ses->ipv6_dns->count;
+ return ses->ipv6_dns->addr;
}
- return count;
+ *count = conf_dns_count;
+ return conf_dns;
}
#endif
diff --git a/accel-pppd/ipv6/dhcpv6.c b/accel-pppd/ipv6/dhcpv6.c
index 9b957f0f..5bbb1e36 100644
--- a/accel-pppd/ipv6/dhcpv6.c
+++ b/accel-pppd/ipv6/dhcpv6.c
@@ -30,7 +30,6 @@
#include "memdebug.h"
#define BUF_SIZE 65536
-#define MAX_DNS_COUNT 3
static struct {
struct dhcpv6_opt_serverid hdr;
@@ -46,8 +45,7 @@ static uint8_t *conf_aftr_gw;
static int conf_aftr_gw_size;
-static struct in6_addr conf_dns[MAX_DNS_COUNT];
-static int conf_dns_count;
+static struct ipv6_dns_t conf_dns;
static uint8_t *conf_dnssl;
static int conf_dnssl_size;
@@ -226,24 +224,30 @@ static int insert_status(struct dhcpv6_packet *pkt, struct dhcpv6_option *opt, i
static int insert_oro(struct dhcpv6_packet *reply, struct dhcpv6_option *opt)
{
struct dhcpv6_option *opt1;
- int i, j, dns_count;
+ int i, dns_count;
uint8_t *ptr;
uint16_t code;
- struct in6_addr addr;
- uint8_t *addr_ptr;
- struct in6_addr dns[MAX_DNS_COUNT];
+ const struct in6_addr *dns;
+ size_t max_dns_count;
for (i = ntohs(opt->hdr->len) / 2, ptr = opt->hdr->data; i; i--, ptr += sizeof(code)) {
code = u_read_be16(ptr);
if (code == D6_OPTION_DNS_SERVERS) {
- dns_count = ipv6_dns_get(reply->ses, conf_dns, conf_dns_count,
- dns, MAX_DNS_COUNT);
+ dns = ipv6_dns_get(reply->ses, conf_dns.addr, conf_dns.count,
+ &dns_count);
+ max_dns_count = dhcpv6_option_space(reply) / sizeof(*dns);
+ if ((size_t)dns_count > max_dns_count) {
+ log_ppp_warn("dhcpv6: sending %zu of %i DNS servers,"
+ " the rest does not fit into the reply\n",
+ max_dns_count, dns_count);
+ dns_count = (int)max_dns_count;
+ }
if (dns_count) {
- opt1 = dhcpv6_option_alloc(reply, D6_OPTION_DNS_SERVERS, dns_count * sizeof(addr));
+ opt1 = dhcpv6_option_alloc(reply, D6_OPTION_DNS_SERVERS,
+ dns_count * sizeof(*dns));
if (!opt1)
return -1;
- for (j = 0, addr_ptr = opt1->hdr->data; j < dns_count; j++, addr_ptr += sizeof(addr))
- memcpy(addr_ptr, dns + j, sizeof(addr));
+ memcpy(opt1->hdr->data, dns, dns_count * sizeof(*dns));
}
} else if (code == D6_OPTION_DOMAIN_LIST) {
if (conf_dnssl_size) {
@@ -1030,7 +1034,7 @@ static void load_dns(void)
if (!s)
return;
- conf_dns_count = 0;
+ conf_dns.count = 0;
if (conf_dnssl)
_free(conf_dnssl);
@@ -1044,14 +1048,17 @@ static void load_dns(void)
}
if (!strcmp(opt->name, "dns") || !opt->val) {
- if (conf_dns_count == MAX_DNS_COUNT)
- continue;
+ if (ipv6_dns_reserve(&conf_dns, conf_dns.count + 1)) {
+ log_emerg("dhcpv6: out of memory allocating IPv6 DNS servers\n");
+ break;
+ }
- if (inet_pton(AF_INET6, opt->val ? opt->val : opt->name, &conf_dns[conf_dns_count]) == 0) {
+ if (inet_pton(AF_INET6, opt->val ? opt->val : opt->name,
+ &conf_dns.addr[conf_dns.count]) == 0) {
log_error("dnsv6: failed to parse '%s'\n", opt->name);
continue;
}
- conf_dns_count++;
+ conf_dns.count++;
}
}
}
diff --git a/accel-pppd/ipv6/dhcpv6.h b/accel-pppd/ipv6/dhcpv6.h
index 7dfc71a1..705870d9 100644
--- a/accel-pppd/ipv6/dhcpv6.h
+++ b/accel-pppd/ipv6/dhcpv6.h
@@ -196,6 +196,7 @@ void dhcpv6_packet_free(struct dhcpv6_packet *pkt);
void dhcpv6_packet_print(struct dhcpv6_packet *pkt, void (*print)(const char *fmt, ...));
struct dhcpv6_packet *dhcpv6_packet_alloc_reply(struct dhcpv6_packet *req, int type);
struct dhcpv6_option *dhcpv6_option_alloc(struct dhcpv6_packet *pkt, int code, int len);
+size_t dhcpv6_option_space(const struct dhcpv6_packet *pkt);
struct dhcpv6_option *dhcpv6_nested_option_alloc(struct dhcpv6_packet *pkt, struct dhcpv6_option *opt, int code, int len);
void dhcpv6_fill_relay_info(struct dhcpv6_packet *pkt);
diff --git a/accel-pppd/ipv6/dhcpv6_packet.c b/accel-pppd/ipv6/dhcpv6_packet.c
index 9190c505..5685b3d3 100644
--- a/accel-pppd/ipv6/dhcpv6_packet.c
+++ b/accel-pppd/ipv6/dhcpv6_packet.c
@@ -1,4 +1,5 @@
#include <stdlib.h>
+#include <stddef.h>
#include <string.h>
#include <arpa/inet.h>
@@ -261,12 +262,31 @@ error:
return NULL;
}
+static size_t dhcpv6_packet_tailroom(const struct dhcpv6_packet *pkt)
+{
+ /* The message, preceded by the relay headers when there are any, lives
+ in the BUF_SIZE bytes allocated right behind the packet. Note that
+ pkt->hdr is not the start of that buffer once relays are involved */
+ ptrdiff_t room = (uint8_t *)(pkt + 1) + BUF_SIZE - (uint8_t *)pkt->endptr;
+
+ return room > 0 ? (size_t)room : 0;
+}
+
+size_t dhcpv6_option_space(const struct dhcpv6_packet *pkt)
+{
+ size_t room = dhcpv6_packet_tailroom(pkt);
+
+ if (room <= sizeof(struct dhcpv6_opt_hdr))
+ return 0;
+
+ return room - sizeof(struct dhcpv6_opt_hdr);
+}
+
struct dhcpv6_option *dhcpv6_option_alloc(struct dhcpv6_packet *pkt, int code, int len)
{
struct dhcpv6_option *opt;
- if (len < 0 || len > BUF_SIZE ||
- (char *)(pkt + 1) + BUF_SIZE - (char *)pkt->endptr < sizeof(struct dhcpv6_opt_hdr) + (size_t)len)
+ if (len < 0 || dhcpv6_packet_tailroom(pkt) < sizeof(struct dhcpv6_opt_hdr) + (size_t)len)
return NULL;
opt = _malloc(sizeof(*opt));
@@ -293,8 +313,7 @@ struct dhcpv6_option *dhcpv6_nested_option_alloc(struct dhcpv6_packet *pkt, stru
{
struct dhcpv6_option *opt;
- if (len < 0 || len > BUF_SIZE ||
- (char *)(pkt + 1) + BUF_SIZE - (char *)pkt->endptr < sizeof(struct dhcpv6_opt_hdr) + (size_t)len)
+ if (len < 0 || dhcpv6_packet_tailroom(pkt) < sizeof(struct dhcpv6_opt_hdr) + (size_t)len)
return NULL;
opt = _malloc(sizeof(*opt));
diff --git a/accel-pppd/ipv6/ipv6_dns_test.c b/accel-pppd/ipv6/ipv6_dns_test.c
index 9452e5a1..3a5c7090 100644
--- a/accel-pppd/ipv6/ipv6_dns_test.c
+++ b/accel-pppd/ipv6/ipv6_dns_test.c
@@ -6,7 +6,8 @@
* a configured build directory around for config.h:
* gcc -O2 -Wall -D_GNU_SOURCE -DAP_SESSIONID_LEN=16 \
* -I accel-pppd/include -I accel-pppd/triton -I build \
- * -o /tmp/ipv6_dns_test accel-pppd/ipv6/ipv6_dns_test.c && /tmp/ipv6_dns_test
+ * -o /tmp/ipv6_dns_test accel-pppd/ipv6/ipv6_dns_test.c \
+ * accel-pppd/ipv6_dns.c && /tmp/ipv6_dns_test
*/
#include <stdio.h>
#include <stdlib.h>
@@ -19,8 +20,6 @@ static int failures;
#define CHECK(cond) do { if (!(cond)) { \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); failures++; } } while (0)
-#define MAX_DNS_COUNT 3 /* as in nd.c and dhcpv6.c */
-
static struct in6_addr a6(const char *str)
{
struct in6_addr addr;
@@ -47,13 +46,11 @@ static struct ap_session *session_with(const char **str, int count)
struct ipv6_dns_t *item = calloc(1, sizeof(*item));
int i;
- INIT_LIST_HEAD(&item->addr_list);
- for (i = 0; i < count; i++) {
- struct ipv6_dns_addr_t *a = calloc(1, sizeof(*a));
-
- a->addr = a6(str[i]);
- list_add_tail(&a->entry, &item->addr_list);
- }
+ item->addr = calloc(count, sizeof(*item->addr));
+ for (i = 0; i < count; i++)
+ item->addr[i] = a6(str[i]);
+ item->count = count;
+ item->capacity = count;
ses->ipv6_dns = item;
@@ -63,13 +60,7 @@ static struct ap_session *session_with(const char **str, int count)
static void session_free(struct ap_session *ses)
{
if (ses->ipv6_dns) {
- while (!list_empty(&ses->ipv6_dns->addr_list)) {
- struct ipv6_dns_addr_t *a = list_entry(ses->ipv6_dns->addr_list.next,
- typeof(*a), entry);
-
- list_del(&a->entry);
- free(a);
- }
+ free(ses->ipv6_dns->addr);
free(ses->ipv6_dns);
}
@@ -80,68 +71,77 @@ int main(void)
{
static const char *four[] = { "2001:db8::1", "2001:db8::2",
"2001:db8::3", "2001:db8::4" };
- struct in6_addr conf_dns[MAX_DNS_COUNT];
- struct in6_addr dns[MAX_DNS_COUNT];
+ struct in6_addr conf_dns[4];
+ struct ipv6_dns_t dynamic_dns = {};
+ const struct in6_addr *dns;
struct ap_session *ses;
int n;
conf_dns[0] = a6("fc00::53");
conf_dns[1] = a6("fc00::54");
+ conf_dns[2] = a6("fc00::55");
+ conf_dns[3] = a6("fc00::56");
+
+ /* Shared storage grows geometrically and retains existing entries. */
+ CHECK(ipv6_dns_reserve(&dynamic_dns, 1) == 0);
+ CHECK(dynamic_dns.capacity == IPV6_DNS_INITIAL_CAPACITY);
+ dynamic_dns.addr[0] = a6("2001:db8::53");
+ CHECK(ipv6_dns_reserve(&dynamic_dns, IPV6_DNS_INITIAL_CAPACITY + 1) == 0);
+ CHECK(dynamic_dns.capacity == IPV6_DNS_INITIAL_CAPACITY * 2);
+ CHECK(is(&dynamic_dns.addr[0], "2001:db8::53"));
+ free(dynamic_dns.addr);
/* No session at all: the configured servers, as before the feature */
- n = ipv6_dns_get(NULL, conf_dns, 2, dns, MAX_DNS_COUNT);
+ dns = ipv6_dns_get(NULL, conf_dns, 2, &n);
CHECK(n == 2);
CHECK(is(&dns[0], "fc00::53"));
CHECK(is(&dns[1], "fc00::54"));
/* Session without assigned servers: same fallback */
ses = calloc(1, sizeof(*ses));
- n = ipv6_dns_get(ses, conf_dns, 2, dns, MAX_DNS_COUNT);
+ dns = ipv6_dns_get(ses, conf_dns, 2, &n);
CHECK(n == 2);
CHECK(is(&dns[0], "fc00::53"));
free(ses);
/* Nothing configured and nothing assigned: advertise nothing */
- n = ipv6_dns_get(NULL, conf_dns, 0, dns, MAX_DNS_COUNT);
+ dns = ipv6_dns_get(NULL, conf_dns, 0, &n);
CHECK(n == 0);
/* Assigned servers win over the configured ones */
ses = session_with(four, 2);
- n = ipv6_dns_get(ses, conf_dns, 2, dns, MAX_DNS_COUNT);
+ dns = ipv6_dns_get(ses, conf_dns, 2, &n);
CHECK(n == 2);
CHECK(is(&dns[0], "2001:db8::1"));
CHECK(is(&dns[1], "2001:db8::2"));
/* ... and win even when nothing is configured */
- n = ipv6_dns_get(ses, conf_dns, 0, dns, MAX_DNS_COUNT);
+ dns = ipv6_dns_get(ses, conf_dns, 0, &n);
CHECK(n == 2);
CHECK(is(&dns[0], "2001:db8::1"));
session_free(ses);
- /* An empty assigned list is "nothing assigned", not "no DNS" */
+ /* An empty assigned set is "nothing assigned", not "no DNS" */
ses = session_with(four, 0);
- n = ipv6_dns_get(ses, conf_dns, 2, dns, MAX_DNS_COUNT);
+ dns = ipv6_dns_get(ses, conf_dns, 2, &n);
CHECK(n == 2);
CHECK(is(&dns[0], "fc00::53"));
session_free(ses);
- /* More assigned than fit: keep the first max, never overrun */
+ /* Assigned sets are not restricted to the configured-server limit */
ses = session_with(four, 4);
- memset(dns, 0, sizeof(dns));
- n = ipv6_dns_get(ses, conf_dns, 2, dns, MAX_DNS_COUNT);
- CHECK(n == MAX_DNS_COUNT);
+ dns = ipv6_dns_get(ses, conf_dns, 2, &n);
+ CHECK(n == 4);
CHECK(is(&dns[0], "2001:db8::1"));
CHECK(is(&dns[1], "2001:db8::2"));
CHECK(is(&dns[2], "2001:db8::3"));
- /* Same for the configured ones, should they ever exceed max */
- n = ipv6_dns_get(NULL, conf_dns, 99, dns, MAX_DNS_COUNT);
- CHECK(n == MAX_DNS_COUNT);
-
- /* A caller with no room gets nothing rather than a stomped buffer */
- n = ipv6_dns_get(ses, conf_dns, 2, dns, 0);
- CHECK(n == 0);
+ /* Configured sets are not restricted to three servers either */
+ dns = ipv6_dns_get(NULL, conf_dns, 4, &n);
+ CHECK(n == 4);
+ CHECK(is(&dns[2], "fc00::55"));
+ CHECK(is(&dns[3], "fc00::56"));
session_free(ses);
diff --git a/accel-pppd/ipv6/nd.c b/accel-pppd/ipv6/nd.c
index 815b822a..674dff53 100644
--- a/accel-pppd/ipv6/nd.c
+++ b/accel-pppd/ipv6/nd.c
@@ -22,13 +22,10 @@
#include "memdebug.h"
-#define MAX_DNS_COUNT 3
-
static int conf_init_ra = 5;
static int conf_init_ra_interval = 3;
static int conf_rdnss_lifetime;
-static struct in6_addr conf_dns[MAX_DNS_COUNT];
-static int conf_dns_count;
+static struct ipv6_dns_t conf_dns;
static uint8_t *conf_dnssl;
static int conf_dnssl_size;
@@ -107,11 +104,12 @@ static void ipv6_nd_send_ra(struct ipv6_nd_handler_t *h, struct sockaddr_in6 *ds
struct nd_opt_dnssl_info_local *dnsslinfo;
//struct nd_opt_mtu *mtu;
struct ipv6db_addr_t *a;
+ const struct in6_addr *dns;
struct in6_addr addr, peer_addr;
- struct in6_addr dns[MAX_DNS_COUNT];
char str[INET6_ADDRSTRLEN];
void *bufend;
int i, prefix_len, dns_count;
+ size_t avail, dnssl_size, max_dns;
if (!buf) {
log_emerg("out of memory\n");
@@ -189,7 +187,31 @@ static void ipv6_nd_send_ra(struct ipv6_nd_handler_t *h, struct sockaddr_in6 *ds
rinfo++;
}*/
- dns_count = ipv6_dns_get(ses, conf_dns, conf_dns_count, dns, MAX_DNS_COUNT);
+ dns = ipv6_dns_get(ses, conf_dns.addr, conf_dns.count, &dns_count);
+
+ /* Room left behind the prefix information options, shared by the RDNSS
+ and DNSSL options which follow. The search list is sized by the
+ configuration alone, so give it its share first */
+ avail = (uint8_t *)bufend - (uint8_t *)pinfo;
+ dnssl_size = conf_dnssl ? (1 + (conf_dnssl_size - 1) / 8 + 1) * 8 : 0;
+ if (dnssl_size > avail) {
+ log_ppp_warn("ipv6_nd: DNS search list does not fit into the router"
+ " advertisement, not advertising it\n");
+ dnssl_size = 0;
+ }
+ avail -= dnssl_size;
+
+ /* nd_opt_rdnssi_len counts 8 byte units in a single octet, so an
+ advertisement carries at most 127 addresses however large it is */
+ max_dns = avail > sizeof(*rdnssinfo) ? (avail - sizeof(*rdnssinfo)) / sizeof(*dns) : 0;
+ if (max_dns > 127)
+ max_dns = 127;
+ if ((size_t)dns_count > max_dns) {
+ log_ppp_warn("ipv6_nd: advertising %zu of %i DNS servers, the rest"
+ " does not fit into the router advertisement\n",
+ max_dns, dns_count);
+ dns_count = (int)max_dns;
+ }
if (dns_count) {
rdnssinfo = (struct nd_opt_rdnss_info_local *)pinfo;
@@ -199,13 +221,13 @@ static void ipv6_nd_send_ra(struct ipv6_nd_handler_t *h, struct sockaddr_in6 *ds
rdnssinfo->nd_opt_rdnssi_lifetime = htonl(conf_rdnss_lifetime);
rdnss_addr = (struct in6_addr *)rdnssinfo->nd_opt_rdnssi;
for (i = 0; i < dns_count; i++) {
- memcpy(rdnss_addr, &dns[i], sizeof(*rdnss_addr));
+ memcpy(rdnss_addr, dns + i, sizeof(*rdnss_addr));
rdnss_addr++;
}
} else
rdnss_addr = (struct in6_addr *)pinfo;
- if (conf_dnssl) {
+ if (dnssl_size) {
dnsslinfo = (struct nd_opt_dnssl_info_local *)rdnss_addr;
memset(dnsslinfo, 0, sizeof(*dnsslinfo));
dnsslinfo->nd_opt_dnssli_type = ND_OPT_DNSSL_INFORMATION;
@@ -484,7 +506,7 @@ static void load_dns(void)
if (!s)
return;
- conf_dns_count = 0;
+ conf_dns.count = 0;
if (conf_dnssl)
_free(conf_dnssl);
@@ -504,14 +526,17 @@ static void load_dns(void)
}
if (!strcmp(opt->name, "dns") || !opt->val) {
- if (conf_dns_count == MAX_DNS_COUNT)
- continue;
+ if (ipv6_dns_reserve(&conf_dns, conf_dns.count + 1)) {
+ log_emerg("ipv6_nd: out of memory allocating IPv6 DNS servers\n");
+ break;
+ }
- if (inet_pton(AF_INET6, opt->val ? opt->val : opt->name, &conf_dns[conf_dns_count]) == 0) {
+ if (inet_pton(AF_INET6, opt->val ? opt->val : opt->name,
+ &conf_dns.addr[conf_dns.count]) == 0) {
log_error("dnsv6: failed to parse '%s'\n", opt->name);
continue;
}
- conf_dns_count++;
+ conf_dns.count++;
}
}
}
diff --git a/accel-pppd/ipv6_dns.c b/accel-pppd/ipv6_dns.c
new file mode 100644
index 00000000..bb1d13f3
--- /dev/null
+++ b/accel-pppd/ipv6_dns.c
@@ -0,0 +1,33 @@
+#include <limits.h>
+
+#include "triton.h"
+#include "ipv6_dns.h"
+
+#include "memdebug.h"
+
+int __export ipv6_dns_reserve(struct ipv6_dns_t *dns, unsigned int count)
+{
+ struct in6_addr *addr;
+ unsigned int capacity;
+
+ if (count <= dns->capacity)
+ return 0;
+
+ capacity = dns->capacity;
+ if (!capacity)
+ capacity = IPV6_DNS_INITIAL_CAPACITY;
+ while (capacity < count) {
+ /* Doubling past this would wrap around and spin forever */
+ if (capacity > UINT_MAX / 2)
+ return -1;
+ capacity *= 2;
+ }
+
+ addr = _realloc(dns->addr, capacity * sizeof(*addr));
+ if (!addr)
+ return -1;
+
+ dns->addr = addr;
+ dns->capacity = capacity;
+ return 0;
+}
diff --git a/accel-pppd/radius/radius.c b/accel-pppd/radius/radius.c
index 544be09f..cb26132e 100644
--- a/accel-pppd/radius/radius.c
+++ b/accel-pppd/radius/radius.c
@@ -488,32 +488,14 @@ 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 ipv6_dns_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;
- struct ipv6_dns_addr_t *dns6;
- int dns6_count = -1;
+ unsigned int dns6_count = 0;
+ unsigned int dns6_index = 0;
int res = 0;
struct radius_pd_t *rpd = req->rpd;
@@ -521,6 +503,16 @@ int rad_proc_attrs(struct rad_req_t *req)
req->rpd->acct_interim_jitter = conf_acct_interim_jitter;
list_for_each_entry(attr, &req->reply->attrs, entry) {
+ if (!attr->vendor && attr->attr->id == DNS_Server_IPv6_Address)
+ dns6_count++;
+ }
+
+ if (dns6_count && ipv6_dns_reserve(&rpd->ipv6_dns, dns6_count)) {
+ log_emerg("radius: out of memory allocating IPv6 DNS servers\n");
+ return -1;
+ }
+
+ list_for_each_entry(attr, &req->reply->attrs, entry) {
if (attr->vendor) {
if (attr->vendor->id == VENDOR_Microsoft) {
switch (attr->attr->id) {
@@ -630,24 +622,7 @@ int rad_proc_attrs(struct rad_req_t *req)
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;
- }
- dns6 = _malloc(sizeof(*dns6));
- dns6->addr = attr->val.ipv6addr;
- list_add_tail(&dns6->entry, &rpd->ipv6_dns.addr_list);
- dns6_count++;
+ rpd->ipv6_dns.addr[dns6_index++] = attr->val.ipv6addr;
break;
case NAS_Port:
rpd->ses->unit_idx = attr->val.integer;
@@ -684,7 +659,9 @@ int rad_proc_attrs(struct rad_req_t *req)
/* Like the IPv4 DNS servers, absent attributes leave whatever a
previous reply assigned in place */
- if (!list_empty(&rpd->ipv6_dns.addr_list))
+ if (dns6_count)
+ rpd->ipv6_dns.count = dns6_count;
+ if (rpd->ipv6_dns.count)
rpd->ses->ipv6_dns = &rpd->ipv6_dns;
return res;
@@ -851,7 +828,6 @@ 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;
@@ -1035,7 +1011,10 @@ static void ses_finished(struct ap_session *ses)
}
ses->ipv6_dns = NULL;
- free_ipv6_dns(rpd);
+ _free(rpd->ipv6_dns.addr);
+ rpd->ipv6_dns.addr = NULL;
+ rpd->ipv6_dns.count = 0;
+ rpd->ipv6_dns.capacity = 0;
fr6 = rpd->fr6;
while (fr6) {