From 7380393df27ec3ae4aacb40b6f9f25f75a11acf5 Mon Sep 17 00:00:00 2001 From: Jon Maloy Date: Wed, 4 Nov 2026 19:11:12 -0500 Subject: passt, pasta: Introduce unified multi-address data structures As preparation for supporting multiple addresses per interface, we replace the single addr/prefix_len fields with an array. The array consists of a new struct inany_addr_entry containing an address and prefix length, both in inany_addr format. Despite some code refactoring, there are only two real functional changes: - The indicated IPv6 prefix length is now properly stored, instead of being ignored and overridden with the hardcoded value 64, as has been the case until now. - Since even IPv4 addresses now are stored in IPv6 format, we also store the corresponding prefix length in that format, i.e. using the range [96,128] instead of [0,32]. In conf_ip6(), the explicit IN6_IS_ADDR_UNSPECIFIED() check on the address at the end of the function is no longer needed: for host-discovered addresses, it is now checked inside the if (!a) block before calling fwd_set_addr(); for user-provided addresses (via -a), validation already rejects unspecified addresses at parse time. Signed-off-by: Jon Maloy Signed-off-by: Stefano Brivio --- arp.c | 21 ++++++++- conf.c | 157 ++++++++++++++++++++++++++++++++++++++------------------------- dhcp.c | 20 ++++++-- dhcpv6.c | 20 ++++++-- fwd.c | 114 ++++++++++++++++++++++++++++++++++++++-------- fwd.h | 4 ++ inany.h | 34 +++++++++++++- ip.h | 2 + ndp.c | 19 ++++++-- passt.h | 63 ++++++++++++++++++++++--- pasta.c | 23 +++++++--- tap.c | 8 +++- 12 files changed, 373 insertions(+), 112 deletions(-) diff --git a/arp.c b/arp.c index bb042e9..08d5a9f 100644 --- a/arp.c +++ b/arp.c @@ -41,6 +41,9 @@ static bool ignore_arp(const struct ctx *c, const struct arphdr *ah, const struct arpmsg *am) { + const struct guest_addr *a; + const struct in_addr *a4; + if (ah->ar_hrd != htons(ARPHRD_ETHER) || ah->ar_pro != htons(ETH_P_IP) || ah->ar_hln != ETH_ALEN || @@ -54,7 +57,11 @@ static bool ignore_arp(const struct ctx *c, return true; /* Don't resolve the guest's assigned address, either. */ - if (!memcmp(am->tip, &c->ip4.addr, sizeof(am->tip))) + a = fwd_get_addr(c, AF_INET, true, CONF_ADDR_ANY); + if (!a) + return false; + a4 = inany_v4(&a->addr); + if (a4 && !memcmp(am->tip, a4, sizeof(am->tip))) return true; return false; @@ -123,12 +130,22 @@ int arp(const struct ctx *c, struct iov_tail *data) */ void arp_send_init_req(const struct ctx *c) { + const struct guest_addr *a; + const struct in_addr *a4; struct { struct ethhdr eh; struct arphdr ah; struct arpmsg am; } __attribute__((__packed__)) req; + a = fwd_get_addr(c, AF_INET, true, CONF_ADDR_ANY); + if (!a) + return; + + a4 = inany_v4(&a->addr); + if (!a4) + return; + /* Ethernet header */ req.eh.h_proto = htons(ETH_P_ARP); memcpy(req.eh.h_dest, MAC_BROADCAST, sizeof(req.eh.h_dest)); @@ -145,7 +162,7 @@ void arp_send_init_req(const struct ctx *c) memcpy(req.am.sha, c->our_tap_mac, sizeof(req.am.sha)); memcpy(req.am.sip, &c->ip4.our_tap_addr, sizeof(req.am.sip)); memcpy(req.am.tha, MAC_BROADCAST, sizeof(req.am.tha)); - memcpy(req.am.tip, &c->ip4.addr, sizeof(req.am.tip)); + memcpy(req.am.tip, a4, sizeof(req.am.tip)); debug("Sending initial ARP request for guest MAC address"); tap_send_single(c, &req, sizeof(req)); diff --git a/conf.c b/conf.c index ef58e56..7be0c79 100644 --- a/conf.c +++ b/conf.c @@ -373,13 +373,19 @@ static uint8_t conf_ip4_prefix(const char *arg) /** * conf_ip4() - Verify or detect IPv4 support, get relevant addresses + * @c: Execution context * @ifi: Host interface to attempt (0 to determine one) - * @ip4: IPv4 context (will be written) * * Return: interface index for IPv4, or 0 on failure. */ -static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4) +static unsigned int conf_ip4(struct ctx *c, unsigned int ifi) { + struct ip4_ctx *ip4 = &c->ip4; + const struct guest_addr *a; + const struct in_addr *a4; + + a = fwd_get_addr(c, AF_INET, true, CONF_ADDR_ANY); + if (!ifi) ifi = nl_get_ext_if(nl_sock, AF_INET); @@ -398,67 +404,75 @@ static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4) } } - if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) { + if (!a) { + struct in_addr addr = { 0 }; + int prefix_len = 0; int rc = nl_addr_get(nl_sock, ifi, AF_INET, - &ip4->addr, &ip4->prefix_len, NULL); + &addr, &prefix_len, NULL); if (rc < 0) { debug("Couldn't discover IPv4 address: %s", strerror_(-rc)); return 0; } + if (IN4_IS_ADDR_UNSPECIFIED(&addr)) { + warn("Host returned address 0.0.0.0"); + return 0; + } + fwd_set_addr(c, &inany_from_v4(addr), CONF_ADDR_HOST, + prefix_len + 96); } - if (!ip4->prefix_len) { - in_addr_t addr = ntohl(ip4->addr.s_addr); - if (IN_CLASSA(addr)) - ip4->prefix_len = (32 - IN_CLASSA_NSHIFT); - else if (IN_CLASSB(addr)) - ip4->prefix_len = (32 - IN_CLASSB_NSHIFT); - else if (IN_CLASSC(addr)) - ip4->prefix_len = (32 - IN_CLASSC_NSHIFT); - else - ip4->prefix_len = 32; - } - - ip4->addr_seen = ip4->addr; - + a = fwd_get_addr(c, AF_INET, true, CONF_ADDR_ANY); + assert(a); + a4 = inany_v4(&a->addr); + assert(a4); + ip4->addr_seen = *a4; ip4->our_tap_addr = ip4->guest_gw; - if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) - return 0; - return ifi; } /** * conf_ip4_local() - Configure IPv4 addresses and attributes for local mode - * @ip4: IPv4 context (will be written) + * @c: Execution context (will be written) */ -static void conf_ip4_local(struct ip4_ctx *ip4) +static void conf_ip4_local(struct ctx *c) { - if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) - ip4->addr = IP4_LL_GUEST_ADDR; - ip4->addr_seen = ip4->addr; + struct ip4_ctx *ip4 = &c->ip4; + const struct guest_addr *a; + + a = fwd_get_addr(c, AF_INET, true, CONF_ADDR_ANY); + + if (!a) { + fwd_set_addr(c, &inany_from_v4(IP4_LL_GUEST_ADDR), + CONF_ADDR_GEN, IP4_LL_PREFIX_LEN + 96); + ip4->addr_seen = IP4_LL_GUEST_ADDR; + } else { + const struct in_addr *a4 = inany_v4(&a->addr); + + assert(a4); + ip4->addr_seen = *a4; + } if (IN4_IS_ADDR_UNSPECIFIED(&ip4->guest_gw)) ip4->guest_gw = IP4_LL_GUEST_GW; ip4->our_tap_addr = ip4->guest_gw; - if (!ip4->prefix_len) - ip4->prefix_len = IP4_LL_PREFIX_LEN; - ip4->no_copy_addrs = ip4->no_copy_routes = true; } /** * conf_ip6() - Verify or detect IPv6 support, get relevant addresses + * @c: Execution context * @ifi: Host interface to attempt (0 to determine one) - * @ip6: IPv6 context (will be written) * * Return: interface index for IPv6, or 0 on failure. */ -static unsigned int conf_ip6(unsigned int ifi, struct ip6_ctx *ip6) +static unsigned int conf_ip6(struct ctx *c, unsigned int ifi) { + struct ip6_ctx *ip6 = &c->ip6; + const struct guest_addr *a; + union inany_addr addr; int prefix_len = 0; int rc; @@ -479,23 +493,30 @@ static unsigned int conf_ip6(unsigned int ifi, struct ip6_ctx *ip6) } } - rc = nl_addr_get(nl_sock, ifi, AF_INET6, - IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) ? &ip6->addr : NULL, + rc = nl_addr_get(nl_sock, ifi, AF_INET6, &addr.a6, &prefix_len, &ip6->our_tap_ll); if (rc < 0) { debug("Couldn't discover IPv6 address: %s", strerror_(-rc)); return 0; } - ip6->addr_seen = ip6->addr; + a = fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY); + if (!a) { + if (IN6_IS_ADDR_UNSPECIFIED(&addr)) + return 0; + + fwd_set_addr(c, &addr, CONF_ADDR_HOST, prefix_len); + ip6->addr_seen = addr.a6; + } else { + ip6->addr_seen = a->addr.a6; + } if (IN6_IS_ADDR_LINKLOCAL(&ip6->guest_gw)) ip6->our_tap_ll = ip6->guest_gw; else ip6->our_tap_addr = ip6->guest_gw; - if (IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) || - IN6_IS_ADDR_UNSPECIFIED(&ip6->our_tap_ll)) + if (IN6_IS_ADDR_UNSPECIFIED(&ip6->our_tap_ll)) return 0; return ifi; @@ -503,10 +524,12 @@ static unsigned int conf_ip6(unsigned int ifi, struct ip6_ctx *ip6) /** * conf_ip6_local() - Configure IPv6 addresses and attributes for local mode - * @ip6: IPv6 context (will be written) + * @c: Execution context (will be written) */ -static void conf_ip6_local(struct ip6_ctx *ip6) +static void conf_ip6_local(struct ctx *c) { + struct ip6_ctx *ip6 = &c->ip6; + if (IN6_IS_ADDR_UNSPECIFIED(&ip6->guest_gw)) ip6->guest_gw = IP6_LL_GUEST_GW; @@ -805,6 +828,7 @@ enum passt_modes conf_mode(int argc, char *argv[]) static void conf_print(const struct ctx *c) { char buf[INANY_ADDRSTRLEN]; + const struct guest_addr *a; int i; if (c->fd_control_listen >= 0) @@ -852,16 +876,19 @@ static void conf_print(const struct ctx *c) inet_ntop(AF_INET, &c->ip4.map_host_loopback, buf, sizeof(buf))); - if (!c->no_dhcp) { + a = fwd_get_addr(c, AF_INET, true, CONF_ADDR_ANY); + if (a && !c->no_dhcp) { uint32_t mask; + int plen = 0; - mask = htonl(0xffffffff << (32 - c->ip4.prefix_len)); + inany_prefix_v4(&a->addr, a->prefix_len, &plen); + mask = IN4_MASK(plen); info("DHCP:"); info(" assign: %s", - inet_ntop(AF_INET, &c->ip4.addr, buf, sizeof(buf))); + inany_ntop(&a->addr, buf, sizeof(buf))); info(" mask: %s", - inet_ntop(AF_INET, &mask, buf, sizeof(buf))); + inet_ntop(AF_INET, &mask, buf, sizeof(buf))); info(" router: %s", inet_ntop(AF_INET, &c->ip4.guest_gw, buf, sizeof(buf))); @@ -876,8 +903,8 @@ static void conf_print(const struct ctx *c) break; if (!i) info("DNS:"); - inet_ntop(AF_INET, &c->ip4.dns[i], buf, sizeof(buf)); - info(" %s", buf); + info(" %s", inet_ntop(AF_INET, &c->ip4.dns[i], + buf, sizeof(buf))); } for (i = 0; *c->dns_search[i].n; i++) { @@ -902,13 +929,14 @@ static void conf_print(const struct ctx *c) else goto dns6; - info(" assign: %s", - inet_ntop(AF_INET6, &c->ip6.addr, buf, sizeof(buf))); + a = fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY); + if (a) + info(" assign: %s", + inany_ntop(&a->addr, buf, sizeof(buf))); info(" router: %s", inet_ntop(AF_INET6, &c->ip6.guest_gw, buf, sizeof(buf))); info(" our link-local: %s", - inet_ntop(AF_INET6, &c->ip6.our_tap_ll, - buf, sizeof(buf))); + inet_ntop(AF_INET6, &c->ip6.our_tap_ll, buf, sizeof(buf))); dns6: for (i = 0; i < ARRAY_SIZE(c->ip6.dns); i++) { @@ -916,8 +944,10 @@ dns6: break; if (!i) info("DNS:"); - inet_ntop(AF_INET6, &c->ip6.dns[i], buf, sizeof(buf)); - info(" %s", buf); + info(" %s", + inet_ntop(AF_INET6, &c->ip6.dns[i], + buf, sizeof(buf))); + } for (i = 0; *c->dns_search[i].n; i++) { @@ -1224,15 +1254,13 @@ static bool conf_addr(struct ctx *c, char *arg, uint8_t opt_n) inany_is_loopback(&addr)) goto bad; + fwd_set_addr(c, &addr, CONF_ADDR_USER, prefix_len); if (a4) { - c->ip4.addr = *a4; - c->ip4.prefix_len = prefix_len - 96; - c->ip4.addr_fixed = true; c->ip4.no_copy_addrs = true; + c->ip4.addr_fixed = true; } else { - c->ip6.addr = addr.a6; - c->ip6.addr_fixed = true; c->ip6.no_copy_addrs = true; + c->ip6.addr_fixed = true; } return is_prefix; @@ -1346,6 +1374,7 @@ void conf(struct ctx *c, int argc, char **argv) bool opt_a_is_prefix = false; const char *logfile = NULL; unsigned long optcode; + struct guest_addr *a; char *runas = NULL; size_t logsize = 0; uint8_t opt_n = 0; @@ -1690,8 +1719,12 @@ void conf(struct ctx *c, int argc, char **argv) if (opt_a_is_prefix) die("Redundant prefix length specification"); - c->ip4.prefix_len = conf_ip4_prefix(optarg); - opt_n = c->ip4.prefix_len + 96; + opt_n = conf_ip4_prefix(optarg) + 96; + + for_each_addr(a, c->addrs, c->addr_count, AF_INET) { + a->prefix_len = opt_n; + break; + } break; case 'M': p = optarg; @@ -1891,9 +1924,9 @@ void conf(struct ctx *c, int argc, char **argv) nl_sock_init(c, false); if (!v6_only && !c->splice_only) - c->ifi4 = conf_ip4(ifi4, &c->ip4); + c->ifi4 = conf_ip4(c, ifi4); if (!v4_only && !c->splice_only) - c->ifi6 = conf_ip6(ifi6, &c->ip6); + c->ifi6 = conf_ip6(c, ifi6); if (c->ifi4 && c->mtu < IPV4_MIN_MTU) { warn("MTU %"PRIu16" is too small for IPv4 (minimum %u)", @@ -1916,7 +1949,7 @@ void conf(struct ctx *c, int argc, char **argv) if (!c->ifi4 && !v6_only) { if (!c->splice_only) { info("IPv4: no external interface as template, use local mode"); - conf_ip4_local(&c->ip4); + conf_ip4_local(c); } c->ifi4 = -1; } @@ -1924,7 +1957,7 @@ void conf(struct ctx *c, int argc, char **argv) if (!c->ifi6 && !v4_only) { if (!c->splice_only) { info("IPv6: no external interface as template, use local mode"); - conf_ip6_local(&c->ip6); + conf_ip6_local(c); } c->ifi6 = -1; } @@ -2000,7 +2033,7 @@ void conf(struct ctx *c, int argc, char **argv) if (!c->ifi6) { c->no_ndp = 1; c->no_dhcpv6 = 1; - } else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) { + } else if (!fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY)) { c->no_dhcpv6 = 1; } diff --git a/dhcp.c b/dhcp.c index 84b8610..a081e34 100644 --- a/dhcp.c +++ b/dhcp.c @@ -754,7 +754,9 @@ static void opt_set_dns_search(const struct ctx *c, size_t max_len) int dhcp(const struct ctx *c, struct iov_tail *data) { char macstr[ETH_ADDRSTRLEN]; + const struct guest_addr *a; size_t mlen, dlen, opt_len; + const struct in_addr *addr; struct in_addr mask, dst; struct ethhdr eh_storage; struct iphdr iph_storage; @@ -767,6 +769,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) bool has_bootfile; struct msg reply; unsigned int i; + int plen; eh = IOV_REMOVE_HEADER(data, eh_storage); iph = IOV_PEEK_HEADER(data, iph_storage); @@ -796,6 +799,15 @@ int dhcp(const struct ctx *c, struct iov_tail *data) m->op != BOOTREQUEST) return -1; + a = fwd_get_addr(c, AF_INET, true, CONF_ADDR_USER); + if (!a) + a = fwd_get_addr(c, AF_INET, true, CONF_ADDR_ANY); + if (!a) + return -1; + + addr = inany_prefix_v4(&a->addr, a->prefix_len, &plen); + assert(addr); + reply.op = BOOTREPLY; reply.htype = m->htype; reply.hlen = m->hlen; @@ -804,7 +816,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) reply.secs = 0; reply.flags = m->flags; reply.ciaddr = m->ciaddr; - reply.yiaddr = c->ip4.addr; + reply.yiaddr = *addr; reply.siaddr = 0; reply.giaddr = m->giaddr; memcpy(&reply.chaddr, m->chaddr, sizeof(reply.chaddr)); @@ -861,7 +873,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) info(" from %s", eth_ntop(m->chaddr, macstr, sizeof(macstr))); - mask.s_addr = htonl(0xffffffff << (32 - c->ip4.prefix_len)); + mask.s_addr = IN4_MASK(plen); if (opts[1].conf == OPT_DEFAULT) memcpy(opts[1].s, &mask, sizeof(mask)); if (opts[3].conf == OPT_DEFAULT) @@ -874,7 +886,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) /* If the gateway is not on the assigned subnet, send an option 121 * (Classless Static Routing) adding a dummy route to it. */ - if ((c->ip4.addr.s_addr & mask.s_addr) + if ((addr->s_addr & mask.s_addr) != (c->ip4.guest_gw.s_addr & mask.s_addr)) { /* a.b.c.d/32:0.0.0.0, 0:a.b.c.d */ opts[121].slen = 14; @@ -953,7 +965,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) if (m->flags & FLAG_BROADCAST) dst = in4addr_broadcast; else - dst = c->ip4.addr; + dst = *addr; tap_udp4_send(c, c->ip4.our_tap_addr, 67, dst, 68, &reply, dlen); diff --git a/dhcpv6.c b/dhcpv6.c index 29c7e32..21b0e6b 100644 --- a/dhcpv6.c +++ b/dhcpv6.c @@ -318,7 +318,7 @@ static bool dhcpv6_opt(struct iov_tail *data, uint16_t type) * false otherwise and @data is unmodified */ static bool dhcpv6_ia_notonlink(struct iov_tail *data, - struct in6_addr *la) + const struct in6_addr *la) { int ia_types[2] = { OPT_IA_NA, OPT_IA_TA }; struct opt_ia_addr opt_addr_storage; @@ -567,6 +567,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, struct opt_hdr client_id_storage; /* cppcheck-suppress [variableScope,unmatchedSuppression] */ struct opt_ia_na ia_storage; + const struct guest_addr *a; const struct in6_addr *src; struct msg_hdr mh_storage; const struct msg_hdr *mh; @@ -584,6 +585,15 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, if (c->no_dhcpv6) return 1; + /* If no address is available, drop all messages rather than sending a + * bogus address. Information-request messages could ideally still be + * answered, but since we unconditionally include an IA_NA option in all + * replies (RFC 9915, Appendix B.), dropping them is safer. + */ + a = fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY); + if (!a) + return 1; + if (!IN6_IS_ADDR_MULTICAST(daddr)) return -1; @@ -627,7 +637,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, if (mh->type == TYPE_CONFIRM && server_id) return -1; - if (dhcpv6_ia_notonlink(data, &c->ip6.addr)) { + if (dhcpv6_ia_notonlink(data, &a->addr.a6)) { dhcpv6_send_ia_notonlink(c, saddr, data, &client_id_base, @@ -667,6 +677,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, default: return -1; } + if (ia) resp.ia_na.iaid = ((struct opt_ia_na *)ia)->iaid; @@ -681,8 +692,9 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, resp.hdr.xid = mh->xid; + resp.ia_addr.addr = a->addr.a6; tap_udp6_send(c, src, 547, saddr, 546, mh->xid, &resp, n); - c->ip6.addr_seen = c->ip6.addr; + c->ip6.addr_seen = a->addr.a6; return 1; } @@ -705,6 +717,4 @@ void dhcpv6_init(const struct ctx *c) c->our_tap_mac, sizeof(c->our_tap_mac)); memcpy(resp_not_on_link.server_id.duid_lladdr, c->our_tap_mac, sizeof(c->our_tap_mac)); - - resp.ia_addr.addr = c->ip6.addr; } diff --git a/fwd.c b/fwd.c index a081664..48764c5 100644 --- a/fwd.c +++ b/fwd.c @@ -243,6 +243,62 @@ void fwd_neigh_table_init(const struct ctx *c) fwd_neigh_table_update(c, &mga, c->our_tap_mac, true); } +/** + * fwd_set_addr() - Add or update an address in the unified address array + * @c: Execution context + * @addr: Address to add (IPv4-mapped or IPv6) + * @flags: CONF_ADDR_* flags for this address + * @prefix_len: Prefix length in IPv6 format (96-128 for IPv4-mapped) + * + * Find the first existing entry of the same address family and + * overwrite it, or create a new one if none exists + */ +void fwd_set_addr(struct ctx *c, const union inany_addr *addr, + uint8_t flags, int prefix_len) +{ + struct guest_addr *a; + + assert(flags); + + for_each_addr(a, c->addrs, c->addr_count, inany_af(addr)) + goto found; + + if (c->addr_count >= MAX_GUEST_ADDRS) + return; + + a = &c->addrs[c->addr_count++]; + +found: + a->addr = *addr; + a->prefix_len = prefix_len; + a->flags = flags; +} + +/** + * fwd_get_addr() - Get guest address entry matching criteria + * @c: Execution context + * @af: Address family (AF_INET, AF_INET6, or AF_UNSPEC for any) + * @global: If true, match global only; if false, match link-local only + * @flags: Required provenance flags (any-match), 0 for any provenance + * + * Return: first address entry matching criteria, or NULL + */ +const struct guest_addr *fwd_get_addr(const struct ctx *c, sa_family_t af, + bool global, uint8_t flags) +{ + const struct guest_addr *a; + + for_each_addr(a, c->addrs, c->addr_count, af) { + if (inany_is_linklocal6(&a->addr) == global) + continue; + if (!(a->flags & flags)) + continue; + return a; + } + + return NULL; +} + /* Forwarding table storage, generally accessed via pointers in struct ctx */ static struct fwd_table fwd_in; static struct fwd_table fwd_out; @@ -799,8 +855,11 @@ static bool is_dns_flow(uint8_t proto, const struct flowside *ini) * translation, false otherwise */ static bool fwd_guest_accessible4(const struct ctx *c, - const struct in_addr *addr) + const struct in_addr *addr) { + const struct guest_addr *a; + const struct in_addr *a4; + if (IN4_IS_ADDR_LOOPBACK(addr)) return false; @@ -815,7 +874,9 @@ static bool fwd_guest_accessible4(const struct ctx *c, /* For IPv4, addr_seen is initialised to addr, so is always a valid * address */ - if (IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr) || + a = fwd_get_addr(c, AF_INET, true, CONF_ADDR_ANY); + a4 = a ? inany_v4(&a->addr) : NULL; + if ((a4 && IN4_ARE_ADDR_EQUAL(addr, a4)) || IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr_seen)) return false; @@ -833,10 +894,13 @@ static bool fwd_guest_accessible4(const struct ctx *c, static bool fwd_guest_accessible6(const struct ctx *c, const struct in6_addr *addr) { + const struct guest_addr *a; + if (IN6_IS_ADDR_LOOPBACK(addr)) return false; - if (IN6_ARE_ADDR_EQUAL(addr, &c->ip6.addr)) + a = fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY); + if (a && IN6_ARE_ADDR_EQUAL(addr, &a->addr.a6)) return false; /* For IPv6, addr_seen starts unspecified, because we don't know what LL @@ -881,16 +945,21 @@ static bool fwd_guest_accessible(const struct ctx *c, static void nat_outbound(const struct ctx *c, const union inany_addr *addr, union inany_addr *translated) { - if (inany_equals4(addr, &c->ip4.map_host_loopback)) + const struct guest_addr *ga; + + if (inany_equals4(addr, &c->ip4.map_host_loopback)) { *translated = inany_loopback4; - else if (inany_equals6(addr, &c->ip6.map_host_loopback)) + } else if (inany_equals6(addr, &c->ip6.map_host_loopback)) { *translated = inany_loopback6; - else if (inany_equals4(addr, &c->ip4.map_guest_addr)) - *translated = inany_from_v4(c->ip4.addr); - else if (inany_equals6(addr, &c->ip6.map_guest_addr)) - translated->a6 = c->ip6.addr; - else + } else if (inany_equals4(addr, &c->ip4.map_guest_addr)) { + ga = fwd_get_addr(c, AF_INET, true, CONF_ADDR_ANY); + *translated = ga ? ga->addr : inany_any4; + } else if (inany_equals6(addr, &c->ip6.map_guest_addr)) { + ga = fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY); + translated->a6 = ga ? ga->addr.a6 : in6addr_any; + } else { *translated = *addr; + } } /** @@ -995,16 +1064,23 @@ bool nat_inbound(const struct ctx *c, const union inany_addr *addr, } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback) && inany_equals6(addr, &in6addr_loopback)) { translated->a6 = c->ip6.map_host_loopback; - } else if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_guest_addr) && - inany_equals4(addr, &c->ip4.addr)) { - *translated = inany_from_v4(c->ip4.map_guest_addr); - } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_guest_addr) && - inany_equals6(addr, &c->ip6.addr)) { - translated->a6 = c->ip6.map_guest_addr; - } else if (fwd_guest_accessible(c, addr)) { - *translated = *addr; } else { - return false; + const struct guest_addr *ga4, *ga6; + + ga4 = fwd_get_addr(c, AF_INET, true, CONF_ADDR_ANY); + ga6 = fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY); + + if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_guest_addr) && + ga4 && inany_equals(addr, &ga4->addr)) { + *translated = inany_from_v4(c->ip4.map_guest_addr); + } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_guest_addr) && + ga6 && inany_equals(addr, &ga6->addr)) { + translated->a6 = c->ip6.map_guest_addr; + } else if (fwd_guest_accessible(c, addr)) { + *translated = *addr; + } else { + return false; + } } return true; diff --git a/fwd.h b/fwd.h index a6c7bd8..1b24428 100644 --- a/fwd.h +++ b/fwd.h @@ -79,5 +79,9 @@ void fwd_neigh_table_free(const struct ctx *c, void fwd_neigh_mac_get(const struct ctx *c, const union inany_addr *addr, uint8_t *mac); void fwd_neigh_table_init(const struct ctx *c); +void fwd_set_addr(struct ctx *c, const union inany_addr *addr, + uint8_t flags, int prefix_len); +const struct guest_addr *fwd_get_addr(const struct ctx *c, sa_family_t af, + bool global, uint8_t flags); #endif /* FWD_H */ diff --git a/inany.h b/inany.h index d0befb1..ecd845a 100644 --- a/inany.h +++ b/inany.h @@ -104,6 +104,36 @@ static inline struct in_addr *inany_v4(const union inany_addr *addr) return (struct in_addr *)&addr->v4mapped.a4; } +/** inany_prefix_v4() - Extract IPv4 address and prefix length from inany + * @addr: IPv4 (as mapped) or IPv6 address + * @prefix_len: Prefix length in IPv6 format (96-128 for IPv4) + * @v4_plen: Set to IPv4 prefix length (0-32) on success + * + * Return: IPv4 address if @addr is IPv4, NULL otherwise + */ +static inline struct in_addr *inany_prefix_v4(const union inany_addr *addr, + int prefix_len, int *v4_plen) +{ + struct in_addr *a4 = inany_v4(addr); + + if (!a4) + return NULL; + + *v4_plen = prefix_len - 96; + + return a4; +} + +/** inany_af() - Get address family of IPv[46] address + * @addr: IPv4 or IPv6 address + * + * Return: AF_INET for IPv4, AF_INET6 for IPv6 + */ +static inline sa_family_t inany_af(const union inany_addr *addr) +{ + return inany_v4(addr) ? AF_INET : AF_INET6; +} + /** inany_default_prefix_len() - Get default prefix length for address * @addr: IPv4 or iPv6 address * @@ -112,9 +142,9 @@ static inline struct in_addr *inany_v4(const union inany_addr *addr) */ static inline int inany_default_prefix_len(const union inany_addr *addr) { - const struct in_addr *v4 = inany_v4(addr); + const struct in_addr *a4 = inany_v4(addr); - return v4 ? ip4_class_prefix_len(v4) + 96 : 64; + return a4 ? ip4_class_prefix_len(a4) + 96 : 64; } /** inany_equals - Compare two IPv[46] addresses diff --git a/ip.h b/ip.h index aab9b86..ac93c54 100644 --- a/ip.h +++ b/ip.h @@ -19,6 +19,8 @@ (ntohl(((struct in_addr *)(a))->s_addr) >> IN_CLASSA_NSHIFT == IN_LOOPBACKNET) #define IN4_IS_ADDR_MULTICAST(a) \ (IN_MULTICAST(ntohl(((struct in_addr *)(a))->s_addr))) +#define IN4_MASK(prefix) \ + ((prefix) <= 0 ? 0 : htonl(0xffffffff << (32 - (prefix)))) #define IN4_ARE_ADDR_EQUAL(a, b) \ (((struct in_addr *)(a))->s_addr == ((struct in_addr *)b)->s_addr) #define IN4ADDR_LOOPBACK_INIT \ diff --git a/ndp.c b/ndp.c index 1f2bcb0..86df479 100644 --- a/ndp.c +++ b/ndp.c @@ -257,7 +257,6 @@ static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) .valid_lifetime = ~0U, .pref_lifetime = ~0U, }, - .prefix = c->ip6.addr, .source_ll = { .header = { .type = OPT_SRC_L2_ADDR, @@ -265,8 +264,15 @@ static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) }, }, }; + const struct guest_addr *a; unsigned char *ptr = NULL; + a = fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY); + if (!a) + return; + + ra.prefix = a->addr.a6; + ptr = &ra.var[0]; if (c->mtu) { @@ -460,6 +466,7 @@ first: */ void ndp_send_init_req(const struct ctx *c) { + const struct guest_addr *a; struct ndp_ns ns = { .ih = { .icmp6_type = NS, @@ -468,8 +475,14 @@ void ndp_send_init_req(const struct ctx *c) .icmp6_solicited = 0, /* Reserved */ .icmp6_override = 0, /* Reserved */ }, - .target_addr = c->ip6.addr + .target_addr = IN6ADDR_ANY_INIT }; + + a = fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY); + if (!a) + return; + + ns.target_addr = a->addr.a6; debug("Sending initial NDP NS request for guest MAC address"); - ndp_send(c, &c->ip6.addr, &ns, sizeof(ns)); + ndp_send(c, &a->addr.a6, &ns, sizeof(ns)); } diff --git a/passt.h b/passt.h index 947e859..d4661d8 100644 --- a/passt.h +++ b/passt.h @@ -64,11 +64,30 @@ enum passt_modes { MODE_VU, }; +/* Maximum number of addresses in context address array */ +#define MAX_GUEST_ADDRS 32 + +/** + * struct guest_addr - Unified IPv4/IPv6 address entry + * @addr: IPv4 (as mapped) or IPv6 address + * @prefix_len: Prefix length in IPv6/IPv4-mapped [0,128]/[96,128] format + * @flags: CONF_ADDR_* flags + */ +struct guest_addr { + union inany_addr addr; + uint8_t prefix_len; + uint8_t flags; +#define CONF_ADDR_USER BIT(0) /* User set via -a */ +#define CONF_ADDR_HOST BIT(1) /* From host */ +#define CONF_ADDR_GEN BIT(2) /* Generated by code */ +#define CONF_ADDR_ANY (CONF_ADDR_USER | \ + CONF_ADDR_HOST | \ + CONF_ADDR_GEN) /* Any provenance */ +}; + /** * struct ip4_ctx - IPv4 execution context - * @addr: IPv4 address assigned to guest * @addr_seen: Latest IPv4 address seen as source from tap - * @prefixlen: IPv4 prefix length (netmask) * @guest_gw: IPv4 gateway as seen by the guest * @map_host_loopback: Outbound connections to this address are NATted to the * host's 127.0.0.1 @@ -87,9 +106,7 @@ enum passt_modes { */ struct ip4_ctx { /* PIF_TAP addresses */ - struct in_addr addr; struct in_addr addr_seen; - int prefix_len; struct in_addr guest_gw; struct in_addr map_host_loopback; struct in_addr map_guest_addr; @@ -110,7 +127,6 @@ struct ip4_ctx { /** * struct ip6_ctx - IPv6 execution context - * @addr: IPv6 address assigned to guest * @addr_seen: Latest IPv6 global/site address seen as source from tap * @addr_ll_seen: Latest IPv6 link-local address seen as source from tap * @guest_gw: IPv6 gateway as seen by the guest @@ -132,7 +148,6 @@ struct ip4_ctx { */ struct ip6_ctx { /* PIF_TAP addresses */ - struct in6_addr addr; struct in6_addr addr_seen; struct in6_addr addr_ll_seen; struct in6_addr guest_gw; @@ -192,6 +207,8 @@ struct ip6_ctx { * @fqdn: Guest FQDN * @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled * @ip6: IPv6 configuration + * @addrs: Unified address array for both IPv4 (mapped) and IPv6 + * @addr_count: Number of active entries in @addrs array * @pasta_ifn: Name of namespace interface for pasta * @pasta_ifi: Index of namespace interface for pasta * @pasta_conf_ns: Configure namespace after creating it @@ -277,6 +294,9 @@ struct ctx { int ifi6; struct ip6_ctx ip6; + struct guest_addr addrs[MAX_GUEST_ADDRS]; + int addr_count; + char pasta_ifn[IF_NAMESIZE]; unsigned int pasta_ifi; int pasta_conf_ns; @@ -323,6 +343,37 @@ struct ctx { extern struct ctx passt_ctx; +/** + * next_addr_idx_() - Find next address index matching family filter + * @addrs: Array of guest addresses + * @count: Number of addresses in array + * @i: Starting index + * @af: Address family filter: AF_INET, AF_INET6, or AF_UNSPEC for all + * + * Return: next matching index, or count if none found + */ +static inline int next_addr_idx_(const struct guest_addr *addrs, int count, + int i, sa_family_t af) +{ + for (; i < count; i++) { + if (af == AF_UNSPEC || af == inany_af(&addrs[i].addr)) + return i; + } + return i; +} + +/** + * for_each_addr() - Iterate over addresses in array + * @a: Pointer variable for current entry (struct guest_addr *) + * @addrs: Array of guest addresses (e.g., c->addrs) + * @count: Number of addresses (e.g., c->addr_count) + * @af: Address family filter: AF_INET, AF_INET6, or 0 for all + */ +#define for_each_addr(a, addrs, count, af) \ + for (int i_ = next_addr_idx_((addrs), (count), 0, (af)); \ + i_ < (count) && ((a) = &(addrs)[i_], true); \ + i_ = next_addr_idx_((addrs), (count), i_ + 1, (af))) + void proto_update_l2_buf(const unsigned char *eth_d); #endif /* PASST_H */ diff --git a/pasta.c b/pasta.c index 10f3ec8..4cd58fd 100644 --- a/pasta.c +++ b/pasta.c @@ -299,23 +299,32 @@ void pasta_start_ns(struct ctx *c, int argc, char *argv[]) * * Return: 0 on success, negative error code on failure */ -static int pasta_conf_addrs(struct ctx *c, sa_family_t af, int ifi, +static int pasta_conf_addrs(const struct ctx *c, sa_family_t af, int ifi, bool no_copy) { + const struct guest_addr *a; + if (!ifi) return 0; if (!no_copy) return nl_addr_dup(nl_sock, ifi, nl_sock_ns, c->pasta_ifi, af); - if (af == AF_INET && !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr)) + a = fwd_get_addr(c, af, true, CONF_ADDR_ANY); + if (!a) + return 0; + + if (af == AF_INET) { + const struct in_addr *a4; + int plen = 0; + + a4 = inany_prefix_v4(&a->addr, a->prefix_len, &plen); return nl_addr_set(nl_sock_ns, c->pasta_ifi, AF_INET, - &c->ip4.addr, c->ip4.prefix_len); - if (af == AF_INET6 && !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) - return nl_addr_set(nl_sock_ns, c->pasta_ifi, AF_INET6, - &c->ip6.addr, 64); + a4, plen); + } - return 0; + return nl_addr_set(nl_sock_ns, c->pasta_ifi, AF_INET6, + &a->addr.a6, a->prefix_len); } /** diff --git a/tap.c b/tap.c index dfa66c7..dd055a4 100644 --- a/tap.c +++ b/tap.c @@ -1005,8 +1005,12 @@ resume: c->ip6.addr_seen = *saddr; } - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) - c->ip6.addr = *saddr; + if (!fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY) && + !fwd_get_addr(c, AF_INET6, false, CONF_ADDR_ANY)) { + union inany_addr addr = { .a6 = *saddr }; + + fwd_set_addr(c, &addr, CONF_ADDR_GEN, 64); + } } else if (!c->ip6.addr_fixed && !IN6_IS_ADDR_UNSPECIFIED(saddr)) { c->ip6.addr_seen = *saddr; -- cgit v1.2.3