diff options
Diffstat (limited to 'ndp.c')
-rw-r--r-- | ndp.c | 226 |
1 files changed, 150 insertions, 76 deletions
@@ -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; +} |