// SPDX-License-Identifier: GPL-2.0-or-later /* PASST - Plug A Simple Socket Transport * for qemu/UNIX domain socket mode * * PASTA - Pack A Subtle Tap Abstraction * for network namespace/tap device mode * * ndp.c - NDP support for PASST * * Copyright (c) 2020-2021 Red Hat GmbH * Author: Stefano Brivio * */ #include #include #include #include #include #include #include #include #include #include #include #include "checksum.h" #include "util.h" #include "ip.h" #include "passt.h" #include "tap.h" #include "log.h" #include "fwd.h" #include "conf.h" #define RT_LIFETIME 65535 #define RS 133 #define RA 134 #define NS 135 #define NA 136 enum ndp_option_types { OPT_SRC_L2_ADDR = 1, OPT_TARGET_L2_ADDR = 2, OPT_PREFIX_INFO = 3, OPT_MTU = 5, OPT_RDNSS_TYPE = 25, OPT_DNSSL_TYPE = 31, }; /** * struct opt_header - Option header * @type: Option type * @len: Option length, in units of 8 bytes */ struct opt_header { uint8_t type; uint8_t len; } __attribute__((packed)); /** * struct opt_l2_addr - Link-layer address * @header: Option header * @mac: MAC address */ struct opt_l2_addr { struct opt_header header; unsigned char mac[ETH_ALEN]; } __attribute__((packed)); /** * struct ndp_na - NDP Neighbor Advertisement (NA) message * @ih: ICMPv6 header * @target_addr: Target IPv6 address * @target_l2_addr: Target link-layer address */ struct ndp_na { struct icmp6hdr ih; struct in6_addr target_addr; struct opt_l2_addr target_l2_addr; } __attribute__((packed)); /** * struct opt_prefix_info - Prefix Information option header * @header: Option header * @prefix_len: The number of leading bits in the Prefix that are valid * @prefix_flags: Flags associated with the prefix * @valid_lifetime: Valid lifetime (ms) * @pref_lifetime: Preferred lifetime (ms) * @reserved: Unused */ struct opt_prefix_info { struct opt_header header; uint8_t prefix_len; uint8_t prefix_flags; uint32_t valid_lifetime; uint32_t pref_lifetime; uint32_t reserved; } __attribute__((packed)); /** * struct ndp_prefix - Complete Prefix Information option with prefix * @info: Prefix Information option header * @prefix: IPv6 prefix */ struct ndp_prefix { struct opt_prefix_info info; struct in6_addr prefix; } __attribute__((__packed__)); /** * struct opt_mtu - Maximum transmission unit (MTU) option * @header: Option header * @reserved: Unused * @value: MTU value, network order */ struct opt_mtu { struct opt_header header; uint16_t reserved; uint32_t value; } __attribute__((packed)); /** * struct rdnss - Recursive DNS Server (RDNSS) option * @header: Option header * @reserved: Unused * @lifetime: Validity time (s) * @dns: List of DNS server addresses */ struct opt_rdnss { struct opt_header header; uint16_t reserved; uint32_t lifetime; struct in6_addr dns[MAXNS + 1]; } __attribute__((packed)); /** * struct dnssl - DNS Search List (DNSSL) option * @header: Option header * @reserved: Unused * @lifetime: Validity time (s) * @domains: List of NULL-seperated search domains */ struct opt_dnssl { struct opt_header header; uint16_t reserved; uint32_t lifetime; unsigned char domains[MAXDNSRCH * NS_MAXDNAME]; } __attribute__((packed)); /** * struct ndp_ra_hdr - NDP Router Advertisement fixed header * @ih: ICMPv6 header * @reachable: Reachability time, after confirmation (ms) * @retrans: Time between retransmitted NS messages (ms) */ struct ndp_ra_hdr { struct icmp6hdr ih; uint32_t reachable; uint32_t retrans; } __attribute__((__packed__)); /* Maximum RA message size: header + prefixes + source_ll + mtu + rdnss + dnssl */ #define NDP_RA_MAX_SIZE (sizeof(struct ndp_ra_hdr) + \ INANY_MAX_ADDRS * sizeof(struct ndp_prefix) + \ sizeof(struct opt_l2_addr) + \ sizeof(struct opt_mtu) + sizeof(struct opt_rdnss) + \ sizeof(struct opt_dnssl)) /** * struct ndp_ns - NDP Neighbor Solicitation (NS) message * @ih: ICMPv6 header * @target_addr: Target IPv6 address */ struct ndp_ns { struct icmp6hdr ih; struct in6_addr target_addr; } __attribute__((packed, aligned(__alignof__(struct in6_addr)))); /** * ndp_send() - Send an NDP message * @c: Execution context * @dst: IPv6 address to send the message to * @buf: ICMPv6 header + message payload * @l4len: Length of message, including ICMPv6 header */ 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, c->our_tap_mac, 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) { union inany_addr tgt; struct ndp_na na = { .ih = { .icmp6_type = NA, .icmp6_code = 0, .icmp6_router = 1, .icmp6_solicited = 1, .icmp6_override = 1, }, .target_addr = *addr, .target_l2_addr = { .header = { .type = OPT_TARGET_L2_ADDR, .len = 1, }, } }; inany_from_af(&tgt, AF_INET6, addr); fwd_neigh_mac_get(c, &tgt, na.target_l2_addr.mac); ndp_send(c, dst, &na, sizeof(na)); } /** * ndp_unsolicited_na() - Send unsolicited NA * @c: Execution context * @addr: IPv6 address to advertise */ void ndp_unsolicited_na(const struct ctx *c, const struct in6_addr *addr) { if (tap_is_ready(c)) ndp_na(c, &in6addr_ll_all_nodes, addr); } /** * ndp_prefix_fill() - Fill prefix options for all suitable addresses * @c: Execution context * @buf: Buffer to write prefix options into * * Fills buffer with Prefix Information options for all non-linklocal, * non-observed addresses with prefix_len == 64 (required for SLAAC). * * Return: number of bytes written */ static size_t ndp_prefix_fill(const struct ctx *c, unsigned char *buf) { int skip = CONF_ADDR_OBSERVED | CONF_ADDR_LINKLOCAL; const struct inany_addr_entry *e; struct ndp_prefix *p; size_t offset = 0; for_each_addr(e, c, AF_INET6) { if (e->flags & skip) continue; /* SLAAC requires /64 prefix */ if (e->prefix_len != 64) continue; p = (struct ndp_prefix *)(buf + offset); p->info.header.type = OPT_PREFIX_INFO; p->info.header.len = 4; /* 4 * 8 = 32 bytes */ p->info.prefix_len = 64; p->info.prefix_flags = 0xc0; /* L, A flags */ p->info.valid_lifetime = ~0U; p->info.pref_lifetime = ~0U; p->info.reserved = 0; p->prefix = e->addr.a6; offset += sizeof(struct ndp_prefix); } return offset; } /** * 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) { unsigned char buf[NDP_RA_MAX_SIZE] __attribute__((__aligned__(__alignof__(struct in6_addr)))); struct ndp_ra_hdr *hdr = (struct ndp_ra_hdr *)buf; struct opt_l2_addr *source_ll; unsigned char *ptr; size_t prefix_len; /* Build RA header */ *hdr = (struct ndp_ra_hdr){ .ih = { .icmp6_type = RA, .icmp6_code = 0, .icmp6_hop_limit = 255, /* RFC 8319 */ .icmp6_rt_lifetime = htons_constant(RT_LIFETIME), .icmp6_addrconf_managed = 1, }, }; /* Fill prefix options */ prefix_len = ndp_prefix_fill(c, buf + sizeof(struct ndp_ra_hdr)); if (prefix_len == 0) { /* No suitable prefixes to advertise */ return; } /* Add source link-layer address option */ ptr = buf + sizeof(struct ndp_ra_hdr) + prefix_len; source_ll = (struct opt_l2_addr *)ptr; source_ll->header.type = OPT_SRC_L2_ADDR; source_ll->header.len = 1; memcpy(source_ll->mac, c->our_tap_mac, ETH_ALEN); ptr += sizeof(struct opt_l2_addr); if (c->mtu) { 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) { size_t dns_s_len = 0; int i, n; for (n = 0; n < ARRAY_SIZE(c->ip6.dns); n++) if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[n])) break; if (n) { struct opt_rdnss *rdnss = (struct opt_rdnss *)ptr; *rdnss = (struct opt_rdnss) { .header = { .type = OPT_RDNSS_TYPE, .len = 1 + 2 * n, }, .lifetime = ~0U, }; for (i = 0; i < n; i++) { rdnss->dns[i] = c->ip6.dns[i]; } ptr += offsetof(struct opt_rdnss, dns) + i * sizeof(rdnss->dns[0]); for (n = 0; *c->dns_search[n].n; n++) dns_s_len += strlen(c->dns_search[n].n) + 2; } if (!c->no_dhcp_dns_search && dns_s_len) { struct opt_dnssl *dnssl = (struct opt_dnssl *)ptr; *dnssl = (struct opt_dnssl) { .header = { .type = OPT_DNSSL_TYPE, .len = DIV_ROUND_UP(dns_s_len, 8) + 1, }, .lifetime = ~0U, }; ptr = dnssl->domains; for (i = 0; i < n; i++) { size_t len; char *dot; *(ptr++) = '.'; len = sizeof(dnssl->domains) - (ptr - dnssl->domains); strncpy((char *)ptr, c->dns_search[i].n, len); for (dot = (char *)ptr - 1; *dot; dot++) { if (*dot == '.') *dot = strcspn(dot + 1, "."); } ptr += strlen(c->dns_search[i].n); *(ptr++) = 0; } memset(ptr, 0, 8 - dns_s_len % 8); /* padding */ ptr += 8 - dns_s_len % 8; } } ndp_send(c, dst, buf, ptr - buf); } /** * ndp() - Check for NDP solicitations, reply as needed * @c: Execution context * @saddr: Source IPv6 address * @data: Single packet with ICMPv6 header * * Return: 0 if not handled here, 1 if handled, -1 on failure */ int ndp(const struct ctx *c, const struct in6_addr *saddr, struct iov_tail *data) { struct icmp6hdr ih_storage; const struct icmp6hdr *ih; ih = IOV_PEEK_HEADER(data, ih_storage); if (!ih) return -1; 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_storage; const struct ndp_ns *ns; ns = IOV_REMOVE_HEADER(data, ns_storage); 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) { 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 (!tap_is_ready(c) || 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; } /** * ndp_send_init_req() - Send initial NDP NS to retrieve guest MAC address * @c: Execution context */ void ndp_send_init_req(const struct ctx *c) { struct inany_addr_entry *e = first_v6(c); struct ndp_ns ns = { .ih = { .icmp6_type = NS, .icmp6_code = 0, .icmp6_router = 0, /* Reserved */ .icmp6_solicited = 0, /* Reserved */ .icmp6_override = 0, /* Reserved */ }, .target_addr = IN6ADDR_ANY_INIT }; if (!e) return; ns.target_addr = e->addr.a6; debug("Sending initial NDP NS request for guest MAC address"); ndp_send(c, &e->addr.a6, &ns, sizeof(ns)); }