diff options
| author | Denys Fedoryshchenko <denys.f@collabora.com> | 2026-10-06 02:24:50 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-10-06 02:24:50 +0300 |
| commit | b8a3c54aa34434312aa0053686ec37a133fb54e6 (patch) | |
| tree | 4dca27724df4224c23fde20ac7ae4502879f5dad | |
| parent | d33028de8b8b520dfde12d9d9d01f4cd576f23b3 (diff) | |
| parent | 2823a07df0c88e9d98472b9248f2e9c3179f6920 (diff) | |
| download | accel-ppp-b8a3c54aa34434312aa0053686ec37a133fb54e6.tar.gz accel-ppp-b8a3c54aa34434312aa0053686ec37a133fb54e6.zip | |
IPv6 DNS fixes
| -rw-r--r-- | .github/workflows/run-tests-asan-ubsan.yml | 3 | ||||
| -rw-r--r-- | accel-pppd/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | accel-pppd/accel-ppp.conf.5 | 6 | ||||
| -rw-r--r-- | accel-pppd/include/ap_session.h | 8 | ||||
| -rw-r--r-- | accel-pppd/include/ipv6_dns.h | 46 | ||||
| -rw-r--r-- | accel-pppd/ipv6/dhcpv6.c | 41 | ||||
| -rw-r--r-- | accel-pppd/ipv6/dhcpv6.h | 1 | ||||
| -rw-r--r-- | accel-pppd/ipv6/dhcpv6_packet.c | 27 | ||||
| -rw-r--r-- | accel-pppd/ipv6/ipv6_dns_test.c | 77 | ||||
| -rw-r--r-- | accel-pppd/ipv6/nd.c | 88 | ||||
| -rw-r--r-- | accel-pppd/ipv6_dns.c | 33 | ||||
| -rw-r--r-- | accel-pppd/radius/radius.c | 62 | ||||
| -rw-r--r-- | accel-pppd/radius/radius_p.h | 3 |
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; |
