diff options
Diffstat (limited to 'accel-pppd/ipv6')
| -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 |
5 files changed, 149 insertions, 85 deletions
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++; } } } |
