summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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/ap_session.h8
-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.c77
-rw-r--r--accel-pppd/ipv6/nd.c88
-rw-r--r--accel-pppd/ipv6_dns.c33
-rw-r--r--accel-pppd/radius/radius.c62
-rw-r--r--accel-pppd/radius/radius_p.h3
13 files changed, 235 insertions, 162 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/ap_session.h b/accel-pppd/include/ap_session.h
index a0733986..3bc8360b 100644
--- a/accel-pppd/include/ap_session.h
+++ b/accel-pppd/include/ap_session.h
@@ -41,6 +41,7 @@
struct ap_session;
struct backup_data;
struct rtnl_link_stats;
+struct ipv6_dns_t;
struct ap_ctrl {
struct triton_context_t *ctx;
@@ -87,10 +88,9 @@ struct ap_session
struct ipv6db_prefix_t *ipv6_dp;
/* Per session IPv6 DNS servers, NULL when none were assigned and the
globally configured ones ([ipv6-dns]) should be advertised instead.
- Only addr_list, and only the addr member of its entries, is
- meaningful here. Owned by whoever sets it, currently the radius
- module from the DNS-Server-IPv6-Address attribute (RFC 6911) */
- struct ipv6db_item_t *ipv6_dns;
+ Owned by whoever sets it, currently the radius module from the
+ DNS-Server-IPv6-Address attribute (RFC 6911) */
+ struct ipv6_dns_t *ipv6_dns;
char *ipv4_pool_name;
char *ipv6_pool_name;
char *dpv6_pool_name;
diff --git a/accel-pppd/include/ipv6_dns.h b/accel-pppd/include/ipv6_dns.h
index b604d006..e82812ce 100644
--- a/accel-pppd/include/ipv6_dns.h
+++ b/accel-pppd/include/ipv6_dns.h
@@ -3,10 +3,18 @@
#include <netinet/in.h>
-#include "list.h"
-#include "ipdb.h"
#include "ap_session.h"
+#define IPV6_DNS_INITIAL_CAPACITY 4
+
+struct ipv6_dns_t {
+ 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.
*
@@ -15,34 +23,20 @@
* 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 ipv6db_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 72b4751c..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;
@@ -44,17 +43,14 @@ static int is(const struct in6_addr *addr, const char *str)
static struct ap_session *session_with(const char **str, int count)
{
struct ap_session *ses = calloc(1, sizeof(*ses));
- struct ipv6db_item_t *item = calloc(1, sizeof(*item));
+ struct ipv6_dns_t *item = calloc(1, sizeof(*item));
int i;
- INIT_LIST_HEAD(&item->addr_list);
- for (i = 0; i < count; i++) {
- struct ipv6db_addr_t *a = calloc(1, sizeof(*a));
-
- a->addr = a6(str[i]);
- a->prefix_len = 128;
- 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;
@@ -64,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 ipv6db_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);
}
@@ -81,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 8b22da14..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,9 +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");
@@ -132,20 +132,31 @@ static void ipv6_nd_send_ra(struct ipv6_nd_handler_t *h, struct sockaddr_in6 *ds
adv->nd_ra_retransmit = htonl(conf_AdvRetransTimer);
pinfo = (struct nd_opt_prefix_info *)(adv + 1);
+ bufend = (uint8_t *)buf + BUF_SIZE;
list_for_each_entry(a, &ses->ipv6->addr_list, entry) {
prefix_len = a->prefix_len == 128 ? 64 : a->prefix_len;
- memset(pinfo, 0, sizeof(*pinfo));
- pinfo->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
- pinfo->nd_opt_pi_len = 4;
- pinfo->nd_opt_pi_prefix_len = prefix_len;
- pinfo->nd_opt_pi_flags_reserved =
- ((a->flag_onlink || conf_AdvPrefixOnLinkFlag) ? ND_OPT_PI_FLAG_ONLINK : 0) |
- ((a->flag_auto || (conf_AdvPrefixAutonomousFlag && prefix_len == 64)) ? ND_OPT_PI_FLAG_AUTO : 0);
- pinfo->nd_opt_pi_valid_time = htonl(conf_AdvPrefixValidLifetime);
- pinfo->nd_opt_pi_preferred_time = htonl(conf_AdvPrefixPreferredLifetime);
- memcpy(&pinfo->nd_opt_pi_prefix, &a->addr, (prefix_len + 7) / 8);
- pinfo->nd_opt_pi_prefix.s6_addr[prefix_len / 8] &= ~(0xff >> (prefix_len % 8));
- pinfo++;
+
+ /* Addresses are installed even when the advertisement is
+ already full, only their prefix information is dropped */
+ if ((void *)(pinfo + 1) > bufend) {
+ log_ppp_warn("ipv6_nd: prefix %s/%i does not fit into the"
+ " router advertisement, not advertising it\n",
+ inet_ntop(AF_INET6, &a->addr, str, sizeof(str)),
+ prefix_len);
+ } else {
+ memset(pinfo, 0, sizeof(*pinfo));
+ pinfo->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
+ pinfo->nd_opt_pi_len = 4;
+ pinfo->nd_opt_pi_prefix_len = prefix_len;
+ pinfo->nd_opt_pi_flags_reserved =
+ ((a->flag_onlink || conf_AdvPrefixOnLinkFlag) ? ND_OPT_PI_FLAG_ONLINK : 0) |
+ ((a->flag_auto || (conf_AdvPrefixAutonomousFlag && prefix_len == 64)) ? ND_OPT_PI_FLAG_AUTO : 0);
+ pinfo->nd_opt_pi_valid_time = htonl(conf_AdvPrefixValidLifetime);
+ pinfo->nd_opt_pi_preferred_time = htonl(conf_AdvPrefixPreferredLifetime);
+ memcpy(&pinfo->nd_opt_pi_prefix, &a->addr, (prefix_len + 7) / 8);
+ pinfo->nd_opt_pi_prefix.s6_addr[prefix_len / 8] &= ~(0xff >> (prefix_len % 8));
+ pinfo++;
+ }
if (!a->installed) {
if (a->prefix_len == 128) {
@@ -176,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;
@@ -186,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;
@@ -471,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);
@@ -491,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 89f1e398..cb26132e 100644
--- a/accel-pppd/radius/radius.c
+++ b/accel-pppd/radius/radius.c
@@ -488,31 +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 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;
+ unsigned int dns6_count = 0;
+ unsigned int dns6_index = 0;
int res = 0;
struct radius_pd_t *rpd = req->rpd;
@@ -520,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) {
@@ -629,26 +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;
- }
- 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++;
+ rpd->ipv6_dns.addr[dns6_index++] = attr->val.ipv6addr;
break;
case NAS_Port:
rpd->ses->unit_idx = attr->val.integer;
@@ -685,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;
@@ -852,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;
@@ -1036,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) {
diff --git a/accel-pppd/radius/radius_p.h b/accel-pppd/radius/radius_p.h
index e4b84740..354d54fc 100644
--- a/accel-pppd/radius/radius_p.h
+++ b/accel-pppd/radius/radius_p.h
@@ -9,6 +9,7 @@
#include "radius.h"
#include "ppp.h"
#include "ipdb.h"
+#include "ipv6_dns.h"
#include "pwdb.h"
struct rad_server_t;
@@ -65,7 +66,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;
+ struct ipv6_dns_t ipv6_dns;
int acct_interim_interval;
int acct_interim_jitter;