summaryrefslogtreecommitdiff
path: root/accel-pppd
diff options
context:
space:
mode:
Diffstat (limited to 'accel-pppd')
-rw-r--r--accel-pppd/accel-ppp.conf2
-rw-r--r--accel-pppd/accel-ppp.conf.56
-rw-r--r--accel-pppd/backup/backup_file.c3
-rw-r--r--accel-pppd/ctrl/l2tp/packet.c141
-rw-r--r--accel-pppd/ctrl/l2tp/packet_test.c491
-rw-r--r--accel-pppd/ctrl/sstp/sstp.c7
-rw-r--r--accel-pppd/include/ap_session.h6
l---------accel-pppd/include/crypto.h1
-rw-r--r--accel-pppd/include/ipv6_dns.h48
-rw-r--r--accel-pppd/ipv6/dhcpv6.c14
-rw-r--r--accel-pppd/ipv6/ipv6_dns_test.c157
-rw-r--r--accel-pppd/ipv6/nd.c14
-rw-r--r--accel-pppd/radius/packet.c7
-rw-r--r--accel-pppd/radius/radius.c50
-rw-r--r--accel-pppd/radius/radius_p.h1
15 files changed, 890 insertions, 58 deletions
diff --git a/accel-pppd/accel-ppp.conf b/accel-pppd/accel-ppp.conf
index e34ae919..c32e7612 100644
--- a/accel-pppd/accel-ppp.conf
+++ b/accel-pppd/accel-ppp.conf
@@ -415,6 +415,8 @@ delegate=fc00:2::/36,48,name=pool3
delegate=fc00:3::/36,48,name=pool4,next=pool3
[ipv6-dns]
+# Used for sessions which were not assigned their own DNS servers, e.g. by the
+# RADIUS DNS-Server-IPv6-Address attribute (RFC 6911)
#fc00:1::1
#fc00:1::2
#fc00:1::3
diff --git a/accel-pppd/accel-ppp.conf.5 b/accel-pppd/accel-ppp.conf.5
index 07fb363d..7c042541 100644
--- a/accel-pppd/accel-ppp.conf.5
+++ b/accel-pppd/accel-ppp.conf.5
@@ -671,6 +671,12 @@ Specifies primary NBNS to be sent to peer.
.BI "wins2=" x.x.x.x
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
+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.
diff --git a/accel-pppd/backup/backup_file.c b/accel-pppd/backup/backup_file.c
index 06dc02d4..472694f0 100644
--- a/accel-pppd/backup/backup_file.c
+++ b/accel-pppd/backup/backup_file.c
@@ -10,11 +10,12 @@
#include <sys/uio.h>
#include <sys/mman.h>
+#include <openssl/md5.h>
+
#include "triton.h"
#include "log.h"
#include "ap_session.h"
#include "backup.h"
-#include "crypto.h"
#include "memdebug.h"
#define VERSION 1
diff --git a/accel-pppd/ctrl/l2tp/packet.c b/accel-pppd/ctrl/l2tp/packet.c
index 1e1488b5..0a4113a0 100644
--- a/accel-pppd/ctrl/l2tp/packet.c
+++ b/accel-pppd/ctrl/l2tp/packet.c
@@ -111,44 +111,80 @@ void l2tp_packet_free(struct l2tp_packet_t *pack)
mempool_free(pack);
}
+/*
+ * AVPs are not aligned in any way inside the packet buffer: their offset
+ * depends on the length of every preceding AVP, which is peer chosen.
+ * Always go through memcpy() to read multi-byte fields out of them, both to
+ * stay portable on strict alignment architectures and to avoid tripping
+ * -fsanitize=alignment.
+ */
+static uint16_t unaligned_ntohs(const void *ptr)
+{
+ uint16_t val;
+
+ memcpy(&val, ptr, sizeof(val));
+
+ return ntohs(val);
+}
+
+static uint32_t unaligned_ntohl(const void *ptr)
+{
+ uint32_t val;
+
+ memcpy(&val, ptr, sizeof(val));
+
+ return ntohl(val);
+}
+
+static uint64_t unaligned_be64toh(const void *ptr)
+{
+ uint64_t val;
+
+ memcpy(&val, ptr, sizeof(val));
+
+ return be64toh(val);
+}
+
+static void unaligned_htons(void *ptr, uint16_t val)
+{
+ val = htons(val);
+ memcpy(ptr, &val, sizeof(val));
+}
+
+static void unaligned_htonl(void *ptr, uint32_t val)
+{
+ val = htonl(val);
+ memcpy(ptr, &val, sizeof(val));
+}
+
+static void unaligned_htobe64(void *ptr, uint64_t val)
+{
+ val = htobe64(val);
+ memcpy(ptr, &val, sizeof(val));
+}
+
static void memxor(uint8_t *dst, const uint8_t *src, size_t sz)
{
- const uintmax_t *umax_src = (const uintmax_t *)src;
- uintmax_t *umax_dst = (uintmax_t *)dst;
- size_t left = sz % sizeof(uintmax_t);
size_t indx;
- for (indx = 0; indx < sz / sizeof(uintmax_t); ++indx)
- umax_dst[indx] ^= umax_src[indx];
-
- src += sz - left;
- dst += sz - left;
- while (left) {
- if (left >= sizeof(uint32_t)) {
- *(uint32_t *)dst ^= *(uint32_t *)src;
- src += sizeof(uint32_t);
- dst += sizeof(uint32_t);
- left -= sizeof(uint32_t);
- } else if (left >= sizeof(uint16_t)) {
- *(uint16_t *)dst ^= *(uint16_t *)src;
- src += sizeof(uint16_t);
- dst += sizeof(uint16_t);
- left -= sizeof(uint16_t);
- } else {
- *dst ^= *src;
- src += sizeof(uint8_t);
- dst += sizeof(uint8_t);
- left -= sizeof(uint8_t);
- }
- }
+ for (indx = 0; indx < sz; ++indx)
+ dst[indx] ^= src[indx];
}
/*
* Decipher hidden AVPs, keeping the Hidden AVP Subformat (i.e. the attribute
* value is prefixed by 2 bytes indicating its length in network byte order).
+ *
+ * On success the deciphered original attribute length is stored into
+ * *orig_attr_len, already validated against the size of the received AVP.
+ * Callers must never re-read that length from the AVP body themselves: it is
+ * the output of the cipher, so a peer using a mismatching secret (or an
+ * attacker blindly injecting hidden AVPs) makes it an essentially random
+ * 16 bits value.
*/
static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
- const char *secret, size_t secret_len)
+ const char *secret, size_t secret_len,
+ uint16_t *orig_attr_len_out)
{
MD5_CTX md5_ctx;
uint8_t md5[MD5_DIGEST_LENGTH];
@@ -162,7 +198,7 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
uint16_t last_block_len;
avp_len = avp->flags & L2TP_AVP_LEN_MASK;
- if (avp_len < sizeof(struct l2tp_avp_t) + 2) {
+ if (avp_len < sizeof(struct l2tp_avp_t) + sizeof(uint16_t)) {
/* Hidden AVPs must contain at least two bytes
for storing original attribute length */
log_warn("l2tp: incorrect hidden avp received (type %hu):"
@@ -180,20 +216,22 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
MD5_Final(p1, &md5_ctx);
if (attr_len <= MD5_DIGEST_LENGTH) {
+ /* The whole attribute fits in the first block: it is fully
+ deciphered, nothing more to do but to check its length */
memxor(avp->val, p1, attr_len);
- return 0;
+ goto out;
}
memxor(p1, avp->val, MD5_DIGEST_LENGTH);
- orig_attr_len = ntohs(*(uint16_t *)p1);
+ orig_attr_len = unaligned_ntohs(p1);
- if (orig_attr_len <= MD5_DIGEST_LENGTH - 2) {
+ if (orig_attr_len <= MD5_DIGEST_LENGTH - sizeof(uint16_t)) {
/* Enough bytes decoded already, no need to decode padding */
memcpy(avp->val, p1, MD5_DIGEST_LENGTH);
- return 0;
+ goto out;
}
- if (orig_attr_len > attr_len - 2) {
+ if (orig_attr_len > attr_len - sizeof(uint16_t)) {
log_warn("l2tp: incorrect hidden avp received (type %hu):"
" original attribute length too big (ciphered"
" attribute length: %hu bytes, advertised original"
@@ -204,7 +242,7 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
/* Decode remaining blocks. Start from the last block as
preceding blocks must be kept hidden for computing MD5s */
- bytes_left = orig_attr_len + 2 - MD5_DIGEST_LENGTH;
+ bytes_left = orig_attr_len + sizeof(uint16_t) - MD5_DIGEST_LENGTH;
last_block_len = bytes_left % MD5_DIGEST_LENGTH;
blocks_left = bytes_left / MD5_DIGEST_LENGTH;
if (last_block_len) {
@@ -228,6 +266,23 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
}
memcpy(avp->val, p1, MD5_DIGEST_LENGTH);
+out:
+ /* The length prefix comes out of the cipher, so it is only as
+ trustworthy as the peer's knowledge of the shared secret. Bound it
+ against the room actually available in the received AVP before
+ letting it drive any read of the attribute value */
+ orig_attr_len = unaligned_ntohs(avp->val);
+ if (orig_attr_len > attr_len - sizeof(uint16_t)) {
+ log_warn("l2tp: incorrect hidden avp received (type %hu):"
+ " deciphered attribute length too big (ciphered"
+ " attribute length: %hu bytes, deciphered original"
+ " attribute length: %hu bytes), wrong secret?\n",
+ ntohs(avp->type), attr_len, orig_attr_len);
+ return -1;
+ }
+
+ *orig_attr_len_out = orig_attr_len;
+
return 0;
}
@@ -241,6 +296,7 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
struct sockaddr_in addr;
socklen_t addr_len;
uint16_t orig_avp_len;
+ uint16_t orig_attr_len;
void *orig_avp_val;
uint8_t *buf, *ptr;
int n, length;
@@ -420,10 +476,11 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
ntohs(avp->type));
goto out_err;
}
- if (decode_avp(avp, RV, secret, secret_len) < 0)
+ if (decode_avp(avp, RV, secret, secret_len,
+ &orig_attr_len) < 0)
goto out_err;
- orig_avp_len = ntohs(*(uint16_t *)avp->val) + sizeof(*avp);
+ orig_avp_len = orig_attr_len + sizeof(*avp);
orig_avp_val = avp->val + sizeof(uint16_t);
} else {
orig_avp_len = avp_len;
@@ -445,17 +502,17 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
case ATTR_TYPE_INT16:
if (orig_avp_len != sizeof(*avp) + 2)
goto out_err_len;
- attr->val.uint16 = ntohs(*(uint16_t *)orig_avp_val);
+ attr->val.uint16 = unaligned_ntohs(orig_avp_val);
break;
case ATTR_TYPE_INT32:
if (orig_avp_len != sizeof(*avp) + 4)
goto out_err_len;
- attr->val.uint32 = ntohl(*(uint32_t *)orig_avp_val);
+ attr->val.uint32 = unaligned_ntohl(orig_avp_val);
break;
case ATTR_TYPE_INT64:
if (orig_avp_len != sizeof(*avp) + 8)
goto out_err_len;
- attr->val.uint64 = be64toh(*(uint64_t *)orig_avp_val);
+ attr->val.uint64 = unaligned_be64toh(orig_avp_val);
break;
case ATTR_TYPE_OCTETS:
attr->val.octets = _malloc(attr->length);
@@ -532,13 +589,13 @@ int l2tp_packet_send(int sock, struct l2tp_packet_t *pack)
else
switch (attr->attr->type) {
case ATTR_TYPE_INT16:
- *(int16_t *)avp->val = htons(attr->val.int16);
+ unaligned_htons(avp->val, attr->val.int16);
break;
case ATTR_TYPE_INT32:
- *(int32_t *)avp->val = htonl(attr->val.int32);
+ unaligned_htonl(avp->val, attr->val.int32);
break;
case ATTR_TYPE_INT64:
- *(uint64_t *)avp->val = htobe64(attr->val.uint64);
+ unaligned_htobe64(avp->val, attr->val.uint64);
break;
case ATTR_TYPE_STRING:
case ATTR_TYPE_OCTETS:
diff --git a/accel-pppd/ctrl/l2tp/packet_test.c b/accel-pppd/ctrl/l2tp/packet_test.c
new file mode 100644
index 00000000..9f962407
--- /dev/null
+++ b/accel-pppd/ctrl/l2tp/packet_test.c
@@ -0,0 +1,491 @@
+/*
+ * Standalone regression test for the L2TP control message parser.
+ *
+ * Not part of the cmake build. Compile and run with:
+ * gcc -O1 -g -Wall -fno-strict-aliasing -D_GNU_SOURCE \
+ * -fsanitize=address,undefined -fno-sanitize-recover=all \
+ * -I accel-pppd/include -I accel-pppd/ctrl/l2tp \
+ * -o /tmp/l2tp_packet_test \
+ * accel-pppd/ctrl/l2tp/packet_test.c accel-pppd/ctrl/l2tp/packet.c \
+ * -lcrypto && /tmp/l2tp_packet_test
+ *
+ * The interesting part is the hidden AVP subformat: the 2 bytes length prefix
+ * of a hidden AVP is an *output of the cipher*, so a peer using a different
+ * secret -- or an attacker injecting hidden AVPs blindly -- turns it into an
+ * essentially random 16 bits value. It must never be trusted to bound a read
+ * of the attribute value.
+ *
+ * The test drives the real parser through a real UDP socket:
+ * - hand-crafted packets exercise the hidden AVP length checks, including
+ * the single block (attribute <= 16 bytes) cipher path which the accel-ppp
+ * encoder itself never produces (it always pads by >= 16 bytes);
+ * - l2tp_packet_send()/l2tp_recv() round trips exercise the multi block
+ * cipher path and the unaligned AVP accessors.
+ *
+ * Everything packet.c needs besides libcrypto is stubbed below.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <openssl/md5.h>
+
+#include "triton.h"
+#include "log.h"
+#include "mempool.h"
+#include "l2tp.h"
+#include "attr_defs.h"
+
+static int failures;
+#define CHECK(cond) do { if (!(cond)) { \
+ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); failures++; } } while (0)
+
+/* ------------------------------------------------------------------ stubs */
+
+int conf_verbose = 1;
+int conf_avp_permissive = 0;
+
+/* A dictionary just big enough for the attributes used here. Types are the
+ ones accel-ppp's own dictionary uses, except Tie_Breaker which is turned
+ into an INT64 to get coverage of the 64 bits accessor. */
+static struct l2tp_dict_attr_t dict[] = {
+ { .name = "Message-Type", .id = Message_Type, .type = ATTR_TYPE_INT16, .M = 1, .H = 0 },
+ { .name = "Tie-Breaker", .id = Tie_Breaker, .type = ATTR_TYPE_INT64, .M = 0, .H = -1 },
+ { .name = "Host-Name", .id = Host_Name, .type = ATTR_TYPE_STRING, .M = 1, .H = -1 },
+ { .name = "Assigned-Tunnel-Id", .id = Assigned_Tunnel_ID, .type = ATTR_TYPE_INT16, .M = 1, .H = -1 },
+ { .name = "Call-Serial-Number", .id = Call_Serial_Number, .type = ATTR_TYPE_INT32, .M = 1, .H = -1 },
+ { .name = "Random-Vector", .id = Random_Vector, .type = ATTR_TYPE_OCTETS, .M = 1, .H = 0 },
+};
+
+struct l2tp_dict_attr_t *l2tp_dict_find_attr_by_id(int id)
+{
+ size_t indx;
+
+ for (indx = 0; indx < sizeof(dict) / sizeof(dict[0]); ++indx)
+ if (dict[indx].id == id)
+ return &dict[indx];
+
+ return NULL;
+}
+
+const struct l2tp_dict_value_t *l2tp_dict_find_value(const struct l2tp_dict_attr_t *attr,
+ l2tp_value_t val)
+{
+ return NULL;
+}
+
+/* Size carrying mempool: allocations stay exactly as large as the pool's
+ object size, so that ASan traps any read past the end of a packet buffer */
+mempool_t *mempool_create(int size)
+{
+ int *pool = malloc(sizeof(int));
+
+ *pool = size;
+
+ return (mempool_t *)pool;
+}
+
+void *mempool_alloc(mempool_t *pool)
+{
+ return malloc(*(int *)pool);
+}
+
+void mempool_free(void *ptr)
+{
+ free(ptr);
+}
+
+void triton_register_init(int order, void (*func)(void))
+{
+ func();
+}
+
+int u_randbuf(void *buf, size_t buf_len, int *err)
+{
+ uint8_t *u8_buf = buf;
+ size_t indx;
+
+ /* Deterministic on purpose: reproducible failures beat real entropy */
+ for (indx = 0; indx < buf_len; ++indx)
+ u8_buf[indx] = (uint8_t)(indx * 7 + 0x5a);
+
+ return 0;
+}
+
+#define DEFINE_LOG_STUB(name) \
+ void name(const char *fmt, ...) {}
+DEFINE_LOG_STUB(log_emerg)
+DEFINE_LOG_STUB(log_error)
+DEFINE_LOG_STUB(log_warn)
+DEFINE_LOG_STUB(log_ppp_debug)
+
+/* -------------------------------------------------------- packet building */
+
+struct pktbuf {
+ uint8_t data[2048];
+ size_t len;
+};
+
+static void pkt_init(struct pktbuf *pkt)
+{
+ struct l2tp_hdr_t hdr;
+
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.flags = htons(L2TP_FLAG_T | L2TP_FLAG_L | L2TP_FLAG_S | 2);
+
+ memset(pkt, 0, sizeof(*pkt));
+ memcpy(pkt->data, &hdr, sizeof(hdr));
+ pkt->len = sizeof(hdr);
+}
+
+/* Append an AVP and return a pointer to its value */
+static uint8_t *pkt_add_avp(struct pktbuf *pkt, uint16_t extra_flags,
+ uint16_t type, const void *val, size_t val_len)
+{
+ struct l2tp_avp_t avp;
+ uint8_t *ptr = pkt->data + pkt->len;
+
+ memset(&avp, 0, sizeof(avp));
+ avp.flags = htons(extra_flags | ((sizeof(avp) + val_len) & L2TP_AVP_LEN_MASK));
+ avp.type = htons(type);
+
+ memcpy(ptr, &avp, sizeof(avp));
+ if (val_len)
+ memcpy(ptr + sizeof(avp), val, val_len);
+ pkt->len += sizeof(avp) + val_len;
+
+ return ptr + sizeof(avp);
+}
+
+static void pkt_finish(struct pktbuf *pkt)
+{
+ uint16_t length = htons(pkt->len);
+
+ memcpy(pkt->data + offsetof(struct l2tp_hdr_t, length),
+ &length, sizeof(length));
+}
+
+/*
+ * Cipher a hidden AVP whose cleartext (length prefix included) is at most one
+ * MD5 block long, i.e. the path that never validated the length prefix.
+ */
+static void hide_single_block(uint8_t *val, size_t val_len, uint16_t type,
+ const char *secret, size_t secret_len,
+ const uint8_t *rv, size_t rv_len)
+{
+ uint8_t md5[MD5_DIGEST_LENGTH];
+ uint16_t attr_type = htons(type);
+ MD5_CTX md5_ctx;
+ size_t indx;
+
+ MD5_Init(&md5_ctx);
+ MD5_Update(&md5_ctx, &attr_type, sizeof(attr_type));
+ MD5_Update(&md5_ctx, secret, secret_len);
+ MD5_Update(&md5_ctx, rv, rv_len);
+ MD5_Final(md5, &md5_ctx);
+
+ for (indx = 0; indx < val_len && indx < MD5_DIGEST_LENGTH; ++indx)
+ val[indx] ^= md5[indx];
+}
+
+/* --------------------------------------------------------------- plumbing */
+
+static const char secret[] = "s3cr3t";
+static int sock = -1;
+static struct sockaddr_in sock_addr;
+
+static void loopback_socket(void)
+{
+ socklen_t addr_len = sizeof(sock_addr);
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ perror("socket");
+ exit(1);
+ }
+
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ if (bind(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0
+ || getsockname(sock, (struct sockaddr *)&sock_addr, &addr_len) < 0) {
+ perror("bind");
+ exit(1);
+ }
+}
+
+/* Feed raw bytes to the parser, NULL means "packet rejected" */
+static struct l2tp_packet_t *parse(const struct pktbuf *pkt)
+{
+ struct l2tp_packet_t *pack = NULL;
+
+ if (sendto(sock, pkt->data, pkt->len, 0,
+ (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) {
+ perror("sendto");
+ exit(1);
+ }
+
+ CHECK(l2tp_recv(sock, &pack, NULL, secret, sizeof(secret) - 1) == 0);
+
+ return pack;
+}
+
+static const struct l2tp_attr_t *find_attr(const struct l2tp_packet_t *pack, int id)
+{
+ const struct l2tp_attr_t *attr;
+
+ list_for_each_entry(attr, &pack->attrs, entry)
+ if (attr->attr->id == id)
+ return attr;
+
+ return NULL;
+}
+
+/* ------------------------------------------------------------------ tests */
+
+/*
+ * A hidden AVP small enough to be ciphered in a single block: its deciphered
+ * length prefix used to be taken at face value, so anything up to 65535 was
+ * handed to the memcpy() feeding attr->val, reading way past the packet
+ * buffer. The parser must accept a prefix only if the attribute value it
+ * announces really fits in the received AVP.
+ */
+static void test_hidden_avp_length_prefix(void)
+{
+ static const struct {
+ const char *name;
+ size_t attr_len; /* ciphered attribute length */
+ uint16_t declared; /* deciphered length prefix */
+ int accept;
+ } cases[] = {
+ { "lies about 64K", 16, 0xffff, 0 },
+ { "lies, minimal avp", 2, 0xffff, 0 },
+ { "off by one", 16, 15, 0 },
+ { "one byte too big", 4, 3, 0 },
+ { "fits exactly", 16, 14, 1 },
+ { "fits", 16, 4, 1 },
+ { "empty value", 2, 0, 1 },
+ { "no length prefix", 1, 0, 0 },
+ };
+ static const uint8_t rv[16] = {
+ 0xf3, 0x1a, 0x00, 0xff, 0x42, 0x7c, 0x91, 0x08,
+ 0x5d, 0xe6, 0x33, 0xb0, 0x14, 0xaa, 0x69, 0xc2,
+ };
+ uint8_t value[MD5_DIGEST_LENGTH];
+ struct l2tp_packet_t *pack;
+ const struct l2tp_attr_t *attr;
+ struct pktbuf pkt;
+ uint16_t declared;
+ size_t indx, i;
+
+ for (indx = 0; indx < sizeof(cases) / sizeof(cases[0]); ++indx) {
+ pkt_init(&pkt);
+ pkt_add_avp(&pkt, L2TP_AVP_FLAG_M, Random_Vector, rv, sizeof(rv));
+
+ /* Cleartext: 2 bytes length prefix, then the value, then padding.
+ The value is a recognizable pattern so that a short read shows
+ up as wrong content rather than as a lucky pass. */
+ memset(value, 0, sizeof(value));
+ declared = htons(cases[indx].declared);
+ memcpy(value, &declared, cases[indx].attr_len < sizeof(declared)
+ ? cases[indx].attr_len : sizeof(declared));
+ for (i = sizeof(declared); i < cases[indx].attr_len; ++i)
+ value[i] = 'a' + (i % 26);
+
+ hide_single_block(value, cases[indx].attr_len, Host_Name,
+ secret, sizeof(secret) - 1, rv, sizeof(rv));
+ pkt_add_avp(&pkt, L2TP_AVP_FLAG_M | L2TP_AVP_FLAG_H, Host_Name,
+ value, cases[indx].attr_len);
+ pkt_finish(&pkt);
+
+ pack = parse(&pkt);
+ if (!cases[indx].accept) {
+ if (pack) {
+ fprintf(stderr, "FAIL %s:%d: hidden avp accepted"
+ " (%s)\n", __FILE__, __LINE__,
+ cases[indx].name);
+ failures++;
+ l2tp_packet_free(pack);
+ }
+ continue;
+ }
+
+ if (!pack) {
+ fprintf(stderr, "FAIL %s:%d: hidden avp rejected (%s)\n",
+ __FILE__, __LINE__, cases[indx].name);
+ failures++;
+ continue;
+ }
+
+ attr = find_attr(pack, Host_Name);
+ CHECK(attr != NULL);
+ if (attr) {
+ CHECK(attr->length == cases[indx].declared);
+ for (i = 0; i < cases[indx].declared; ++i)
+ CHECK((uint8_t)attr->val.string[i] ==
+ 'a' + ((i + sizeof(declared)) % 26));
+ CHECK(attr->val.string[cases[indx].declared] == '\0');
+ }
+ l2tp_packet_free(pack);
+ }
+}
+
+/*
+ * A hidden AVP is rejected outright when no Random Vector was received, or
+ * when its length cannot even hold the length prefix.
+ */
+static void test_hidden_avp_prerequisites(void)
+{
+ static const uint8_t value[16] = { 0 };
+ struct l2tp_packet_t *pack;
+ struct pktbuf pkt;
+
+ pkt_init(&pkt);
+ pkt_add_avp(&pkt, L2TP_AVP_FLAG_M | L2TP_AVP_FLAG_H, Host_Name,
+ value, sizeof(value));
+ pkt_finish(&pkt);
+ pack = parse(&pkt);
+ CHECK(pack == NULL);
+ if (pack)
+ l2tp_packet_free(pack);
+
+ /* Random Vector present, but the hidden AVP carries no value at all */
+ pkt_init(&pkt);
+ pkt_add_avp(&pkt, L2TP_AVP_FLAG_M, Random_Vector, value, sizeof(value));
+ pkt_add_avp(&pkt, L2TP_AVP_FLAG_M | L2TP_AVP_FLAG_H, Host_Name, NULL, 0);
+ pkt_finish(&pkt);
+ pack = parse(&pkt);
+ CHECK(pack == NULL);
+ if (pack)
+ l2tp_packet_free(pack);
+}
+
+/*
+ * Round trip through the real encoder. With hide_avps set every attribute but
+ * Message-Type and Random-Vector goes through the multi block cipher, since
+ * encode_attr() always appends at least 16 bytes of padding.
+ */
+static void test_roundtrip(int hide_avps)
+{
+ static const char host_name[] = "accel-ppp regression test host name";
+ struct l2tp_packet_t *pack;
+ const struct l2tp_attr_t *attr;
+ int ret;
+
+ pack = l2tp_packet_alloc(2, Message_Type_Hello, &sock_addr, hide_avps,
+ secret, sizeof(secret) - 1);
+ CHECK(pack != NULL);
+ if (!pack)
+ return;
+
+ /* Odd length string first: everything after it sits on an odd offset,
+ so the integer accessors below run unaligned */
+ CHECK(l2tp_packet_add_string(pack, Host_Name, host_name, 1) == 0);
+ CHECK(l2tp_packet_add_int16(pack, Assigned_Tunnel_ID, 0x1234, 1) == 0);
+ CHECK(l2tp_packet_add_int32(pack, Call_Serial_Number, 0x89abcdef, 1) == 0);
+ CHECK(l2tp_packet_add_int64(pack, Tie_Breaker, 0x0123456789abcdefULL, 0) == 0);
+
+ ret = l2tp_packet_send(sock, pack);
+ CHECK(ret == 0);
+ l2tp_packet_free(pack);
+ if (ret < 0)
+ return;
+
+ pack = NULL;
+ CHECK(l2tp_recv(sock, &pack, NULL, secret, sizeof(secret) - 1) == 0);
+ CHECK(pack != NULL);
+ if (!pack)
+ return;
+
+ attr = find_attr(pack, Message_Type);
+ CHECK(attr && attr->val.uint16 == Message_Type_Hello);
+ attr = find_attr(pack, Host_Name);
+ CHECK(attr && attr->length == (int)strlen(host_name));
+ CHECK(attr && strcmp(attr->val.string, host_name) == 0);
+ attr = find_attr(pack, Assigned_Tunnel_ID);
+ CHECK(attr && attr->val.uint16 == 0x1234);
+ attr = find_attr(pack, Call_Serial_Number);
+ CHECK(attr && attr->val.uint32 == 0x89abcdef);
+ attr = find_attr(pack, Tie_Breaker);
+ CHECK(attr && attr->val.uint64 == 0x0123456789abcdefULL);
+
+ l2tp_packet_free(pack);
+}
+
+/*
+ * A hidden AVP deciphered with the wrong secret yields a random length
+ * prefix. Whatever it is, the parser must not read outside the AVP.
+ */
+static void test_wrong_secret(void)
+{
+ struct l2tp_packet_t *pack;
+ struct pktbuf pkt;
+ uint8_t buf[1024];
+ size_t len, indx;
+ int ret;
+
+ pack = l2tp_packet_alloc(2, Message_Type_Hello, &sock_addr, 1,
+ secret, sizeof(secret) - 1);
+ CHECK(pack != NULL);
+ if (!pack)
+ return;
+
+ CHECK(l2tp_packet_add_string(pack, Host_Name, "hidden", 1) == 0);
+ CHECK(l2tp_packet_send(sock, pack) == 0);
+ l2tp_packet_free(pack);
+
+ len = recv(sock, buf, sizeof(buf), 0);
+ CHECK(len > 0);
+
+ /* Same bytes on the wire, every other secret at the receiving end */
+ for (indx = 0; indx < 64; ++indx) {
+ char wrong[8];
+
+ snprintf(wrong, sizeof(wrong), "wrong%02zu", indx);
+ memcpy(pkt.data, buf, len);
+ pkt.len = len;
+
+ if (sendto(sock, pkt.data, pkt.len, 0,
+ (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) {
+ perror("sendto");
+ exit(1);
+ }
+ pack = NULL;
+ ret = l2tp_recv(sock, &pack, NULL, wrong, strlen(wrong));
+ CHECK(ret == 0);
+ if (pack) {
+ /* Accepting is fine (the random prefix may happen to be
+ plausible), reading out of the AVP is not */
+ const struct l2tp_attr_t *attr = find_attr(pack, Host_Name);
+
+ CHECK(!attr || attr->length <= (int)len);
+ l2tp_packet_free(pack);
+ }
+ }
+}
+
+int main(void)
+{
+ loopback_socket();
+
+ test_hidden_avp_length_prefix();
+ test_hidden_avp_prerequisites();
+ test_roundtrip(0);
+ test_roundtrip(1);
+ test_wrong_secret();
+
+ close(sock);
+
+ if (failures) {
+ fprintf(stderr, "%d failure(s)\n", failures);
+ return 1;
+ }
+
+ printf("all tests passed\n");
+
+ return 0;
+}
diff --git a/accel-pppd/ctrl/sstp/sstp.c b/accel-pppd/ctrl/sstp/sstp.c
index 2fd8cb35..db08ae93 100644
--- a/accel-pppd/ctrl/sstp/sstp.c
+++ b/accel-pppd/ctrl/sstp/sstp.c
@@ -21,8 +21,11 @@
#include "linux_ppp.h"
/*
- * Suppress OpenSSL 3.0 deprecation warnings for DH API.
- * See crypto.h for detailed explanation.
+ * Suppress OpenSSL 3.0 deprecation warnings for the DH API: it is deprecated
+ * but still functional, and still required for protocol compatibility. The
+ * project sets this for every target (see the top level CMakeLists.txt); it
+ * is repeated here because it only takes effect if it is defined before the
+ * first OpenSSL header is pulled in.
*/
#define OPENSSL_API_COMPAT 0x10100000L
#include <openssl/ssl.h>
diff --git a/accel-pppd/include/ap_session.h b/accel-pppd/include/ap_session.h
index a4d3b867..a0733986 100644
--- a/accel-pppd/include/ap_session.h
+++ b/accel-pppd/include/ap_session.h
@@ -85,6 +85,12 @@ struct ap_session
struct ipv4db_item_t *ipv4;
struct ipv6db_item_t *ipv6;
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;
char *ipv4_pool_name;
char *ipv6_pool_name;
char *dpv6_pool_name;
diff --git a/accel-pppd/include/crypto.h b/accel-pppd/include/crypto.h
deleted file mode 120000
index 2f3f63cb..00000000
--- a/accel-pppd/include/crypto.h
+++ /dev/null
@@ -1 +0,0 @@
-../../crypto/crypto.h \ No newline at end of file
diff --git a/accel-pppd/include/ipv6_dns.h b/accel-pppd/include/ipv6_dns.h
new file mode 100644
index 00000000..b604d006
--- /dev/null
+++ b/accel-pppd/include/ipv6_dns.h
@@ -0,0 +1,48 @@
+#ifndef __IPV6_DNS_H
+#define __IPV6_DNS_H
+
+#include <netinet/in.h>
+
+#include "list.h"
+#include "ipdb.h"
+#include "ap_session.h"
+
+/*
+ * Pick the IPv6 DNS servers to advertise to a session.
+ *
+ * A session may have been assigned its own servers (currently by the radius
+ * module, from the DNS-Server-IPv6-Address attribute of RFC 6911); those take
+ * 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.
+ */
+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)
+{
+ 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++;
+ }
+
+ return count;
+}
+
+#endif
diff --git a/accel-pppd/ipv6/dhcpv6.c b/accel-pppd/ipv6/dhcpv6.c
index b284df5f..4a80bdb9 100644
--- a/accel-pppd/ipv6/dhcpv6.c
+++ b/accel-pppd/ipv6/dhcpv6.c
@@ -20,6 +20,7 @@
#include "log.h"
#include "ppp.h"
#include "ipdb.h"
+#include "ipv6_dns.h"
#include "events.h"
#include "iputils.h"
@@ -220,16 +221,19 @@ static void insert_status(struct dhcpv6_packet *pkt, struct dhcpv6_option *opt,
static void insert_oro(struct dhcpv6_packet *reply, struct dhcpv6_option *opt)
{
struct dhcpv6_option *opt1;
- int i, j;
+ int i, j, dns_count;
uint16_t *ptr;
struct in6_addr addr, *addr_ptr;
+ struct in6_addr dns[MAX_DNS_COUNT];
for (i = ntohs(opt->hdr->len) / 2, ptr = (uint16_t *)opt->hdr->data; i; i--, ptr++) {
if (ntohs(*ptr) == D6_OPTION_DNS_SERVERS) {
- if (conf_dns_count) {
- opt1 = dhcpv6_option_alloc(reply, D6_OPTION_DNS_SERVERS, conf_dns_count * sizeof(addr));
- for (j = 0, addr_ptr = (struct in6_addr *)opt1->hdr->data; j < conf_dns_count; j++, addr_ptr++)
- memcpy(addr_ptr, conf_dns + j, sizeof(addr));
+ dns_count = ipv6_dns_get(reply->ses, conf_dns, conf_dns_count,
+ dns, MAX_DNS_COUNT);
+ if (dns_count) {
+ opt1 = dhcpv6_option_alloc(reply, D6_OPTION_DNS_SERVERS, dns_count * sizeof(addr));
+ for (j = 0, addr_ptr = (struct in6_addr *)opt1->hdr->data; j < dns_count; j++, addr_ptr++)
+ memcpy(addr_ptr, dns + j, sizeof(addr));
}
} else if (ntohs(*ptr) == D6_OPTION_DOMAIN_LIST) {
if (conf_dnssl_size) {
diff --git a/accel-pppd/ipv6/ipv6_dns_test.c b/accel-pppd/ipv6/ipv6_dns_test.c
new file mode 100644
index 00000000..72b4751c
--- /dev/null
+++ b/accel-pppd/ipv6/ipv6_dns_test.c
@@ -0,0 +1,157 @@
+/*
+ * Standalone test for the IPv6 DNS server selection shared by the ipv6_nd and
+ * ipv6_dhcp modules.
+ *
+ * Not part of the cmake build. Compile and run from the top of the tree, with
+ * 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
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "ipv6_dns.h"
+
+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;
+
+ if (inet_pton(AF_INET6, str, &addr) != 1) {
+ fprintf(stderr, "bad address %s\n", str);
+ exit(1);
+ }
+
+ return addr;
+}
+
+static int is(const struct in6_addr *addr, const char *str)
+{
+ struct in6_addr expect = a6(str);
+
+ return memcmp(addr, &expect, sizeof(expect)) == 0;
+}
+
+/* A session carrying 'count' DNS servers taken from '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));
+ 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);
+ }
+
+ ses->ipv6_dns = item;
+
+ return ses;
+}
+
+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);
+ }
+
+ free(ses);
+}
+
+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 ap_session *ses;
+ int n;
+
+ conf_dns[0] = a6("fc00::53");
+ conf_dns[1] = a6("fc00::54");
+
+ /* No session at all: the configured servers, as before the feature */
+ n = ipv6_dns_get(NULL, conf_dns, 2, dns, MAX_DNS_COUNT);
+ 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);
+ 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);
+ 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);
+ 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);
+ CHECK(n == 2);
+ CHECK(is(&dns[0], "2001:db8::1"));
+
+ session_free(ses);
+
+ /* An empty assigned list is "nothing assigned", not "no DNS" */
+ ses = session_with(four, 0);
+ n = ipv6_dns_get(ses, conf_dns, 2, dns, MAX_DNS_COUNT);
+ CHECK(n == 2);
+ CHECK(is(&dns[0], "fc00::53"));
+ session_free(ses);
+
+ /* More assigned than fit: keep the first max, never overrun */
+ 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);
+ 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);
+
+ session_free(ses);
+
+ if (failures) {
+ fprintf(stderr, "%d failure(s)\n", failures);
+ return 1;
+ }
+
+ printf("all tests passed\n");
+
+ return 0;
+}
diff --git a/accel-pppd/ipv6/nd.c b/accel-pppd/ipv6/nd.c
index 6dd00ee2..8b22da14 100644
--- a/accel-pppd/ipv6/nd.c
+++ b/accel-pppd/ipv6/nd.c
@@ -17,6 +17,7 @@
#include "events.h"
#include "mempool.h"
#include "ipdb.h"
+#include "ipv6_dns.h"
#include "iputils.h"
#include "memdebug.h"
@@ -107,7 +108,8 @@ static void ipv6_nd_send_ra(struct ipv6_nd_handler_t *h, struct sockaddr_in6 *ds
//struct nd_opt_mtu *mtu;
struct ipv6db_addr_t *a;
struct in6_addr addr, peer_addr;
- int i, prefix_len;
+ struct in6_addr dns[MAX_DNS_COUNT];
+ int i, prefix_len, dns_count;
if (!buf) {
log_emerg("out of memory\n");
@@ -174,15 +176,17 @@ static void ipv6_nd_send_ra(struct ipv6_nd_handler_t *h, struct sockaddr_in6 *ds
rinfo++;
}*/
- if (conf_dns_count) {
+ dns_count = ipv6_dns_get(ses, conf_dns, conf_dns_count, dns, MAX_DNS_COUNT);
+
+ if (dns_count) {
rdnssinfo = (struct nd_opt_rdnss_info_local *)pinfo;
memset(rdnssinfo, 0, sizeof(*rdnssinfo));
rdnssinfo->nd_opt_rdnssi_type = ND_OPT_RDNSS_INFORMATION;
- rdnssinfo->nd_opt_rdnssi_len = 1 + 2 * conf_dns_count;
+ rdnssinfo->nd_opt_rdnssi_len = 1 + 2 * dns_count;
rdnssinfo->nd_opt_rdnssi_lifetime = htonl(conf_rdnss_lifetime);
rdnss_addr = (struct in6_addr *)rdnssinfo->nd_opt_rdnssi;
- for (i = 0; i < conf_dns_count; i++) {
- memcpy(rdnss_addr, &conf_dns[i], sizeof(*rdnss_addr));
+ for (i = 0; i < dns_count; i++) {
+ memcpy(rdnss_addr, &dns[i], sizeof(*rdnss_addr));
rdnss_addr++;
}
} else
diff --git a/accel-pppd/radius/packet.c b/accel-pppd/radius/packet.c
index cfc0bc29..c7e91349 100644
--- a/accel-pppd/radius/packet.c
+++ b/accel-pppd/radius/packet.c
@@ -10,8 +10,11 @@
#include <arpa/inet.h>
/*
- * Suppress OpenSSL 3.0 deprecation warnings for HMAC API.
- * See crypto.h for detailed explanation.
+ * Suppress OpenSSL 3.0 deprecation warnings for the HMAC API: it is
+ * deprecated but still functional, and still required for protocol
+ * compatibility. The project sets this for every target (see the top level
+ * CMakeLists.txt); it is repeated here because it only takes effect if it is
+ * defined before the first OpenSSL header is pulled in.
*/
#define OPENSSL_API_COMPAT 0x10100000L
#include <openssl/hmac.h>
diff --git a/accel-pppd/radius/radius.c b/accel-pppd/radius/radius.c
index cb53f59a..fa77a916 100644
--- a/accel-pppd/radius/radius.c
+++ b/accel-pppd/radius/radius.c
@@ -488,12 +488,31 @@ err:
return -1;
}
+/*
+ * Number of IPv6 DNS servers kept per session. Matches the number of dns=
+ * options the ipv6_nd and ipv6_dhcp modules accept in [ipv6-dns], and keeps
+ * the RDNSS option of a router advertisement to a sane size.
+ */
+#define MAX_DNS6_COUNT 3
+
+static void free_ipv6_dns(struct radius_pd_t *rpd)
+{
+ struct ipv6db_addr_t *a;
+
+ while (!list_empty(&rpd->ipv6_dns.addr_list)) {
+ a = list_entry(rpd->ipv6_dns.addr_list.next, typeof(*a), entry);
+ list_del(&a->entry);
+ _free(a);
+ }
+}
+
int rad_proc_attrs(struct rad_req_t *req)
{
struct ev_wins_t wins = {};
struct ev_dns_t dns = {};
struct rad_attr_t *attr;
struct ipv6db_addr_t *a;
+ int dns6_count = -1;
int res = 0;
struct radius_pd_t *rpd = req->rpd;
@@ -602,6 +621,28 @@ int rad_proc_attrs(struct rad_req_t *req)
a->addr = attr->val.ipv6prefix.prefix;
list_add_tail(&a->entry, &rpd->ipv6_dp.prefix_list);
break;
+ case DNS_Server_IPv6_Address:
+ if (dns6_count < 0) {
+ /* This reply carries a DNS server list of
+ its own, it replaces whatever a previous
+ one assigned */
+ free_ipv6_dns(rpd);
+ dns6_count = 0;
+ }
+ if (dns6_count >= MAX_DNS6_COUNT) {
+ if (dns6_count == MAX_DNS6_COUNT)
+ log_ppp_warn("radius: ignoring DNS-Server-IPv6-Address"
+ " beyond the first %i\n", MAX_DNS6_COUNT);
+ dns6_count++;
+ break;
+ }
+ a = _malloc(sizeof(*a));
+ memset(a, 0, sizeof(*a));
+ a->prefix_len = 128;
+ a->addr = attr->val.ipv6addr;
+ list_add_tail(&a->entry, &rpd->ipv6_dns.addr_list);
+ dns6_count++;
+ break;
case NAS_Port:
rpd->ses->unit_idx = attr->val.integer;
break;
@@ -635,6 +676,11 @@ int rad_proc_attrs(struct rad_req_t *req)
if (!rpd->ses->ipv6_dp && !list_empty(&rpd->ipv6_dp.prefix_list))
rpd->ses->ipv6_dp = &rpd->ipv6_dp;
+ /* Like the IPv4 DNS servers, absent attributes leave whatever a
+ previous reply assigned in place */
+ if (!list_empty(&rpd->ipv6_dns.addr_list))
+ rpd->ses->ipv6_dns = &rpd->ipv6_dns;
+
return res;
}
@@ -799,6 +845,7 @@ static void ses_starting(struct ap_session *ses)
INIT_LIST_HEAD(&rpd->plugin_list);
INIT_LIST_HEAD(&rpd->ipv6_addr.addr_list);
INIT_LIST_HEAD(&rpd->ipv6_dp.prefix_list);
+ INIT_LIST_HEAD(&rpd->ipv6_dns.addr_list);
rpd->ipv4_addr.owner = &ipdb;
rpd->ipv6_addr.owner = &ipdb;
@@ -981,6 +1028,9 @@ static void ses_finished(struct ap_session *ses)
_free(a);
}
+ ses->ipv6_dns = NULL;
+ free_ipv6_dns(rpd);
+
fr6 = rpd->fr6;
while (fr6) {
struct framed_ip6_route *next = fr6->next;
diff --git a/accel-pppd/radius/radius_p.h b/accel-pppd/radius/radius_p.h
index d3a72204..e4b84740 100644
--- a/accel-pppd/radius/radius_p.h
+++ b/accel-pppd/radius/radius_p.h
@@ -65,6 +65,7 @@ struct radius_pd_t {
struct ipv4db_item_t ipv4_addr;
struct ipv6db_item_t ipv6_addr;
struct ipv6db_prefix_t ipv6_dp;
+ struct ipv6db_item_t ipv6_dns;
int acct_interim_interval;
int acct_interim_jitter;