aboutgitcodebugslistschat
path: root/ndp.c
diff options
context:
space:
mode:
Diffstat (limited to 'ndp.c')
-rw-r--r--ndp.c226
1 files changed, 150 insertions, 76 deletions
diff --git a/ndp.c b/ndp.c
index 6dcb487..37bf7a3 100644
--- a/ndp.c
+++ b/ndp.c
@@ -33,6 +33,8 @@
#include "tap.h"
#include "log.h"
+#define RT_LIFETIME 65535
+
#define RS 133
#define RA 134
#define NS 135
@@ -158,7 +160,7 @@ struct ndp_ra {
unsigned char var[sizeof(struct opt_mtu) + sizeof(struct opt_rdnss) +
sizeof(struct opt_dnssl)];
-} __attribute__((packed));
+} __attribute__((packed, aligned(__alignof__(struct in6_addr))));
/**
* struct ndp_ns - NDP Neighbor Solicitation (NS) message
@@ -168,19 +170,31 @@ struct ndp_ra {
struct ndp_ns {
struct icmp6hdr ih;
struct in6_addr target_addr;
-} __attribute__((packed));
+} __attribute__((packed, aligned(__alignof__(struct in6_addr))));
/**
- * ndp() - Check for NDP solicitations, reply as needed
+ * ndp_send() - Send an NDP message
* @c: Execution context
- * @ih: ICMPv6 header
- * @saddr: Source IPv6 address
- * @p: Packet pool
- *
- * Return: 0 if not handled here, 1 if handled, -1 on failure
+ * @dst: IPv6 address to send the message to
+ * @buf: ICMPv6 header + message payload
+ * @l4len: Length of message, including ICMPv6 header
*/
-int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
- const struct pool *p)
+static void ndp_send(const struct ctx *c, const struct in6_addr *dst,
+ const void *buf, size_t l4len)
+{
+ const struct in6_addr *src = &c->ip6.our_tap_ll;
+
+ tap_icmp6_send(c, src, dst, buf, l4len);
+}
+
+/**
+ * ndp_na() - Send an NDP Neighbour Advertisement (NA) message
+ * @c: Execution context
+ * @dst: IPv6 address to send the NA to
+ * @addr: IPv6 address to advertise
+ */
+static void ndp_na(const struct ctx *c, const struct in6_addr *dst,
+ const struct in6_addr *addr)
{
struct ndp_na na = {
.ih = {
@@ -190,6 +204,7 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
.icmp6_solicited = 1,
.icmp6_override = 1,
},
+ .target_addr = *addr,
.target_l2_addr = {
.header = {
.type = OPT_TARGET_L2_ADDR,
@@ -197,13 +212,26 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
},
}
};
+
+ memcpy(na.target_l2_addr.mac, c->our_tap_mac, ETH_ALEN);
+
+ ndp_send(c, dst, &na, sizeof(na));
+}
+
+/**
+ * ndp_ra() - Send an NDP Router Advertisement (RA) message
+ * @c: Execution context
+ * @dst: IPv6 address to send the RA to
+ */
+static void ndp_ra(const struct ctx *c, const struct in6_addr *dst)
+{
struct ndp_ra ra = {
.ih = {
.icmp6_type = RA,
.icmp6_code = 0,
.icmp6_hop_limit = 255,
/* RFC 8319 */
- .icmp6_rt_lifetime = htons_constant(65535),
+ .icmp6_rt_lifetime = htons_constant(RT_LIFETIME),
.icmp6_addrconf_managed = 1,
},
.prefix_info = {
@@ -216,6 +244,7 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
.valid_lifetime = ~0U,
.pref_lifetime = ~0U,
},
+ .prefix = c->ip6.addr,
.source_ll = {
.header = {
.type = OPT_SRC_L2_ADDR,
@@ -223,59 +252,26 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
},
},
};
- const struct in6_addr *rsaddr; /* src addr for reply */
unsigned char *ptr = NULL;
- size_t dlen;
- if (ih->icmp6_type < RS || ih->icmp6_type > NA)
- return 0;
-
- if (c->no_ndp)
- return 1;
-
- if (ih->icmp6_type == NS) {
- struct ndp_ns *ns = packet_get(p, 0, 0, sizeof(struct ndp_ns),
- NULL);
-
- if (!ns)
- return -1;
-
- if (IN6_IS_ADDR_UNSPECIFIED(saddr))
- return 1;
+ ptr = &ra.var[0];
- info("NDP: received NS, sending NA");
-
- memcpy(&na.target_addr, &ns->target_addr,
- sizeof(na.target_addr));
- memcpy(na.target_l2_addr.mac, c->mac, ETH_ALEN);
+ if (c->mtu != -1) {
+ struct opt_mtu *mtu = (struct opt_mtu *)ptr;
+ *mtu = (struct opt_mtu) {
+ .header = {
+ .type = OPT_MTU,
+ .len = 1,
+ },
+ .value = htonl(c->mtu),
+ };
+ ptr += sizeof(struct opt_mtu);
+ }
- } else if (ih->icmp6_type == RS) {
+ if (!c->no_dhcp_dns) {
size_t dns_s_len = 0;
int i, n;
- if (c->no_ra)
- return 1;
-
- info("NDP: received RS, sending RA");
- memcpy(&ra.prefix, &c->ip6.addr, sizeof(ra.prefix));
-
- ptr = &ra.var[0];
-
- if (c->mtu != -1) {
- struct opt_mtu *mtu = (struct opt_mtu *)ptr;
- *mtu = (struct opt_mtu) {
- .header = {
- .type = OPT_MTU,
- .len = 1,
- },
- .value = htonl(c->mtu),
- };
- ptr += sizeof(struct opt_mtu);
- }
-
- if (c->no_dhcp_dns)
- goto dns_done;
-
for (n = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[n]); n++);
if (n) {
struct opt_rdnss *rdnss = (struct opt_rdnss *)ptr;
@@ -287,8 +283,7 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
.lifetime = ~0U,
};
for (i = 0; i < n; i++) {
- memcpy(&rdnss->dns[i], &c->ip6.dns[i],
- sizeof(rdnss->dns[i]));
+ rdnss->dns[i] = c->ip6.dns[i];
}
ptr += offsetof(struct opt_rdnss, dns) +
i * sizeof(rdnss->dns[0]);
@@ -329,30 +324,109 @@ int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
memset(ptr, 0, 8 - dns_s_len % 8); /* padding */
ptr += 8 - dns_s_len % 8;
}
-
-dns_done:
- memcpy(&ra.source_ll.mac, c->mac, ETH_ALEN);
- } else {
- return 1;
}
- if (IN6_IS_ADDR_LINKLOCAL(saddr))
- c->ip6.addr_ll_seen = *saddr;
- else
- c->ip6.addr_seen = *saddr;
+ memcpy(&ra.source_ll.mac, c->our_tap_mac, ETH_ALEN);
+
+ ndp_send(c, dst, &ra, ptr - (unsigned char *)&ra);
+}
+
+/**
+ * ndp() - Check for NDP solicitations, reply as needed
+ * @c: Execution context
+ * @ih: ICMPv6 header
+ * @saddr: Source IPv6 address
+ * @p: Packet pool
+ *
+ * Return: 0 if not handled here, 1 if handled, -1 on failure
+ */
+int ndp(const struct ctx *c, const struct icmp6hdr *ih,
+ const struct in6_addr *saddr, const struct pool *p)
+{
+ if (ih->icmp6_type < RS || ih->icmp6_type > NA)
+ return 0;
- if (IN6_IS_ADDR_LINKLOCAL(&c->ip6.gw))
- rsaddr = &c->ip6.gw;
- else
- rsaddr = &c->ip6.addr_ll;
+ if (c->no_ndp)
+ return 1;
if (ih->icmp6_type == NS) {
- dlen = sizeof(struct ndp_na);
- tap_icmp6_send(c, rsaddr, saddr, &na, dlen);
+ const struct ndp_ns *ns;
+
+ ns = packet_get(p, 0, 0, sizeof(struct ndp_ns), NULL);
+ if (!ns)
+ return -1;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(saddr))
+ return 1;
+
+ info("NDP: received NS, sending NA");
+
+ ndp_na(c, saddr, &ns->target_addr);
} else if (ih->icmp6_type == RS) {
- dlen = ptr - (unsigned char *)&ra;
- tap_icmp6_send(c, rsaddr, saddr, &ra, dlen);
+ if (c->no_ra)
+ return 1;
+
+ info("NDP: received RS, sending RA");
+ ndp_ra(c, saddr);
}
return 1;
}
+
+/* Default interval between unsolicited RAs (seconds) */
+#define DEFAULT_MAX_RTR_ADV_INTERVAL 600 /* RFC 4861, 6.2.1 */
+
+/* Minimum required interval between RAs (seconds) */
+#define MIN_DELAY_BETWEEN_RAS 3 /* RFC 4861, 10 */
+
+static time_t next_ra;
+
+/**
+ * ndp_timer() - Send unsolicited NDP messages if necessary
+ * @c: Execution context
+ * @now: Current (monotonic) time
+ */
+void ndp_timer(const struct ctx *c, const struct timespec *now)
+{
+ time_t max_rtr_adv_interval = DEFAULT_MAX_RTR_ADV_INTERVAL;
+ time_t min_rtr_adv_interval, interval;
+
+ if (c->fd_tap < 0 || c->no_ra || now->tv_sec < next_ra)
+ return;
+
+ /* We must advertise before the route's lifetime expires */
+ max_rtr_adv_interval = MIN(max_rtr_adv_interval, RT_LIFETIME - 1);
+
+ /* But we must not go smaller than the minimum delay */
+ max_rtr_adv_interval = MAX(max_rtr_adv_interval, MIN_DELAY_BETWEEN_RAS);
+
+ /* RFC 4861, 6.2.1 */
+ min_rtr_adv_interval = MAX(max_rtr_adv_interval / 3,
+ MIN_DELAY_BETWEEN_RAS);
+
+ /* As required by RFC 4861, we randomise the interval between
+ * unsolicited RAs. This is to prevent multiple routers on a link
+ * getting synchronised (e.g. after booting a bunch of routers at once)
+ * and causing flurries of RAs at the same time.
+ *
+ * This random doesn't need to be cryptographically strong, so random(3)
+ * is fine. Other routers on the link also want to avoid
+ * synchronisation, and anything malicious has much easier ways to cause
+ * trouble.
+ *
+ * The modulus also makes this not strictly a uniform distribution, but,
+ * again, it's close enough for our purposes.
+ */
+ interval = min_rtr_adv_interval +
+ random() % (max_rtr_adv_interval - min_rtr_adv_interval);
+
+ if (!next_ra)
+ goto first;
+
+ info("NDP: sending unsolicited RA, next in %llds", (long long)interval);
+
+ ndp_ra(c, &in6addr_ll_all_nodes);
+
+first:
+ next_ra = now->tv_sec + interval;
+}