On Sun, 18 Jan 2026 17:16:05 -0500
Jon Maloy
As preparation for supporting multiple addresses per interface, we replace the single addr/prefix_len fields with arrays.
- We add an ip4_addr_entry and an ip6_addr_entry struct containing address and prefix length.
- We set the array sizes to IP4_MAX_ADDRS=8 and IP6_MAX_ADDRS=16, respectively.
Is there a particular reason behind this choice? Wouldn't it be more convenient, especially for documentation purposes and to avoid confusing users, to stick to the same number of addresses for both versions?
The only functional change is that the IPv6 prefix length now is properly stored instead of being hardcoded to 64 even when set via the -a option.
Signed-off-by: Jon Maloy
--- v2: Using inany_addr instead of protocol specific addresses as entry address field.
No particular comments here, just three minor nits below, but, in general: is the plan to have a single array for both IPv4 and IPv6? If not, does it really make sense to use the inany type for arrays that only contain IPv4 or IPv6 addresses, separately?
--- arp.c | 4 +-- conf.c | 78 +++++++++++++++++++++++++++++++++++--------------------- dhcp.c | 8 +++--- dhcpv6.c | 6 ++--- fwd.c | 12 ++++----- inany.h | 16 ++++++++++++ ip.h | 4 +++ ndp.c | 6 ++--- passt.h | 16 +++++++----- pasta.c | 12 +++++---- tap.c | 4 +-- 11 files changed, 106 insertions(+), 60 deletions(-)
diff --git a/arp.c b/arp.c index bb042e9..bc77a9f 100644 --- a/arp.c +++ b/arp.c @@ -54,7 +54,7 @@ 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))) + if (!memcmp(am->tip, inany_v4(&c->ip4.addrs[0].addr), sizeof(am->tip))) return true;
return false; @@ -145,7 +145,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, inany_v4(&c->ip4.addrs[0].addr), sizeof(req.am.tip));
Nit: it doesn't look like a table anymore, so either drop all the tabs, or mix tabs and spaces: 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, inany_v4(&c->ip4.addrs[0].addr), 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 7178a0e..9fc5dca 100644 --- a/conf.c +++ b/conf.c @@ -763,33 +763,33 @@ static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4) } }
- if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) { + if (!ip4->addr_count) { + struct in_addr addr; + 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; } + ip4->addrs[0].addr = inany_from_v4(addr); + ip4->addrs[0].prefix_len = prefix_len; + ip4->addrs[0].flags = INANY_ADDR_HOST; + ip4->addr_count = 1; }
- 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; + if (!ip4->addrs[0].prefix_len) { + const struct in_addr *a4 = inany_v4(&ip4->addrs[0].addr); + + ip4->addrs[0].prefix_len = ip4_default_prefix_len(a4); }
- ip4->addr_seen = ip4->addr; + ip4->addr_seen = *inany_v4(&ip4->addrs[0].addr);
ip4->our_tap_addr = ip4->guest_gw;
- if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) + if (inany_is_unspecified(&ip4->addrs[0].addr)) return 0;
return ifi; @@ -801,9 +801,11 @@ static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4) */ static void conf_ip4_local(struct ip4_ctx *ip4) { - ip4->addr_seen = ip4->addr = IP4_LL_GUEST_ADDR; + ip4->addrs[0].addr = inany_from_v4(IP4_LL_GUEST_ADDR); + ip4->addr_seen = *inany_v4(&ip4->addrs[0].addr); ip4->our_tap_addr = ip4->guest_gw = IP4_LL_GUEST_GW; - ip4->prefix_len = IP4_LL_PREFIX_LEN; + ip4->addrs[0].prefix_len = IP4_LL_PREFIX_LEN; + ip4->addr_count = 1;
ip4->no_copy_addrs = ip4->no_copy_routes = true; } @@ -838,19 +840,25 @@ 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, + ip6->addr_count ? NULL : &ip6->addrs[0].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; + if (!ip6->addr_count) { + ip6->addrs[0].prefix_len = prefix_len ? prefix_len : 64; + ip6->addrs[0].flags = INANY_ADDR_HOST; + ip6->addr_count = 1; + } + + ip6->addr_seen = ip6->addrs[0].addr.a6;
if (IN6_IS_ADDR_LINKLOCAL(&ip6->guest_gw)) ip6->our_tap_ll = ip6->guest_gw;
- if (IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) || + if (IN6_IS_ADDR_UNSPECIFIED(&ip6->addrs[0].addr.a6) || IN6_IS_ADDR_UNSPECIFIED(&ip6->our_tap_ll)) return 0;
@@ -1193,11 +1201,13 @@ static void conf_print(const struct ctx *c) if (!c->no_dhcp) { uint32_t mask;
- mask = htonl(0xffffffff << (32 - c->ip4.prefix_len)); + mask = htonl(0xffffffff << + (32 - c->ip4.addrs[0].prefix_len));
info("DHCP:"); info(" assign: %s", - inet_ntop(AF_INET, &c->ip4.addr, buf4, sizeof(buf4))); + inet_ntop(AF_INET, inany_v4(&c->ip4.addrs[0].addr), + buf4, sizeof(buf4))); info(" mask: %s", inet_ntop(AF_INET, &mask, buf4, sizeof(buf4))); info(" router: %s", @@ -1235,7 +1245,8 @@ static void conf_print(const struct ctx *c) goto dns6;
info(" assign: %s", - inet_ntop(AF_INET6, &c->ip6.addr, buf6, sizeof(buf6))); + inet_ntop(AF_INET6, &c->ip6.addrs[0].addr.a6, + buf6, sizeof(buf6))); info(" router: %s", inet_ntop(AF_INET6, &c->ip6.guest_gw, buf6, sizeof(buf6))); info(" our link-local: %s", @@ -1870,15 +1881,20 @@ void conf(struct ctx *c, int argc, char **argv) die("Invalid address: %s", optarg);
if (af == AF_INET6) { - c->ip6.addr = addr.a6; + c->ip6.addrs[0].addr.a6 = addr.a6; + c->ip6.addrs[0].flags |= INANY_ADDR_CONFIGURED; + c->ip6.addr_count = 1; if (c->mode == MODE_PASTA) c->ip6.no_copy_addrs = true; break; }
a4 = inany_v4(&addr); + if (af == AF_INET && a4) { - c->ip4.addr = *a4; + c->ip4.addrs[0].addr = inany_from_v4(*a4); + c->ip4.addrs[0].flags |= INANY_ADDR_CONFIGURED; + c->ip4.addr_count = 1; if (prefix_len) { if (prefix_from_opt) die("Can't mix CIDR with -n"); @@ -1886,21 +1902,25 @@ void conf(struct ctx *c, int argc, char **argv) } else { prefix_len = ip4_default_prefix_len(a4); } - c->ip4.prefix_len = prefix_len; + c->ip4.addrs[0].prefix_len = prefix_len; break; }
die("Invalid address: %s", optarg); break; } - case 'n': + case 'n': { + int plen; + if (prefix_from_cidr) die("Can't use both -n and CIDR prefix length"); - c->ip4.prefix_len = conf_ip4_prefix(optarg); - if (c->ip4.prefix_len < 0) + plen = conf_ip4_prefix(optarg); + if (plen < 0) die("Invalid netmask: %s", optarg); + c->ip4.addrs[0].prefix_len = plen; prefix_from_opt = true; break; + } case 'M': parse_mac(c->our_tap_mac, optarg); break; @@ -2185,7 +2205,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 (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addrs[0].addr.a6)) { c->no_dhcpv6 = 1; }
diff --git a/dhcp.c b/dhcp.c index 6b9c2e3..d2afc3b 100644 --- a/dhcp.c +++ b/dhcp.c @@ -352,7 +352,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 = *inany_v4(&c->ip4.addrs[0].addr); reply.siaddr = 0; reply.giaddr = m->giaddr; memcpy(&reply.chaddr, m->chaddr, sizeof(reply.chaddr)); @@ -404,7 +404,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 = htonl(0xffffffff << (32 - c->ip4.addrs[0].prefix_len)); memcpy(opts[1].s, &mask, sizeof(mask)); memcpy(opts[3].s, &c->ip4.guest_gw, sizeof(c->ip4.guest_gw)); memcpy(opts[54].s, &c->ip4.our_tap_addr, sizeof(c->ip4.our_tap_addr)); @@ -412,7 +412,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 ((inany_v4(&c->ip4.addrs[0].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; @@ -469,7 +469,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 = *inany_v4(&c->ip4.addrs[0].addr);
tap_udp4_send(c, c->ip4.our_tap_addr, 67, dst, 68, &reply, dlen);
diff --git a/dhcpv6.c b/dhcpv6.c index e4df0db..f45dece 100644 --- a/dhcpv6.c +++ b/dhcpv6.c @@ -625,7 +625,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, &c->ip6.addrs[0].addr.a6)) {
dhcpv6_send_ia_notonlink(c, data, &client_id_base, ntohs(client_id->l), mh->xid); @@ -679,7 +679,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
tap_udp6_send(c, src, 547, tap_ip6_daddr(c, src), 546, mh->xid, &resp, n); - c->ip6.addr_seen = c->ip6.addr; + c->ip6.addr_seen = c->ip6.addrs[0].addr.a6;
return 1; } @@ -703,5 +703,5 @@ void dhcpv6_init(const struct ctx *c) 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; + resp.ia_addr.addr = c->ip6.addrs[0].addr.a6; } diff --git a/fwd.c b/fwd.c index 44a0e10..8d8151b 100644 --- a/fwd.c +++ b/fwd.c @@ -516,7 +516,7 @@ 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) || + if (IN4_ARE_ADDR_EQUAL(addr, inany_v4(&c->ip4.addrs[0].addr)) || IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr_seen)) return false;
@@ -537,7 +537,7 @@ static bool fwd_guest_accessible6(const struct ctx *c, if (IN6_IS_ADDR_LOOPBACK(addr)) return false;
- if (IN6_ARE_ADDR_EQUAL(addr, &c->ip6.addr)) + if (IN6_ARE_ADDR_EQUAL(addr, &c->ip6.addrs[0].addr.a6)) return false;
/* For IPv6, addr_seen starts unspecified, because we don't know what LL @@ -587,9 +587,9 @@ static void nat_outbound(const struct ctx *c, const union inany_addr *addr, 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); + *translated = c->ip4.addrs[0].addr; else if (inany_equals6(addr, &c->ip6.map_guest_addr)) - translated->a6 = c->ip6.addr; + translated->a6 = c->ip6.addrs[0].addr.a6; else *translated = *addr; } @@ -710,10 +710,10 @@ bool nat_inbound(const struct ctx *c, const union inany_addr *addr, 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)) { + inany_equals(addr, &c->ip4.addrs[0].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)) { + inany_equals6(addr, &c->ip6.addrs[0].addr.a6)) { translated->a6 = c->ip6.map_guest_addr; } else if (fwd_guest_accessible(c, addr)) { *translated = *addr; diff --git a/inany.h b/inany.h index 36865f9..07bfc3d 100644 --- a/inany.h +++ b/inany.h @@ -297,4 +297,20 @@ const char *inany_ntop(const union inany_addr *src, char *dst, socklen_t size); int inany_pton(const char *src, union inany_addr *dst); int inany_prefix_pton(const char *src, union inany_addr *dst, int *prefix_len);
+/* Flags for struct inany_addr_entry */ +#define INANY_ADDR_CONFIGURED (1 << 0) /* User set via -a */ +#define INANY_ADDR_HOST (1 << 1) /* From host interface */ + +/** + * struct inany_addr_entry - Unified IPv4/IPv6 address entry + * @addr: IPv4 (as mapped) or IPv6 address + * @prefix_len: Prefix length (0-32 for IPv4, 0-128 for IPv6)
This should be 1-32 and 1-128, unless you find a way to make /0 assignments working (see my comment on 1/9).
+ * @flags: INANY_ADDR_* flags + */ +struct inany_addr_entry { + union inany_addr addr; + uint16_t prefix_len; + uint16_t flags;
Not that it really matters, but if you're trying to keep it small, uint8_t is enough for both, and if you're trying to keep it simple, 'int' would be the right choice for 'prefix_len'.
+}; + #endif /* INANY_H */ diff --git a/ip.h b/ip.h index e5f5198..78bd43a 100644 --- a/ip.h +++ b/ip.h @@ -135,6 +135,10 @@ static const struct in_addr in4addr_broadcast = { 0xffffffff }; #define IPV6_MIN_MTU 1280 #endif
+/* Maximum number of addresses per address family */ +#define IP4_MAX_ADDRS 8 +#define IP6_MAX_ADDRS 16 + int ip4_default_prefix_len(const struct in_addr *addr);
#endif /* IP_H */ diff --git a/ndp.c b/ndp.c index eb9e313..5248fd6 100644 --- a/ndp.c +++ b/ndp.c @@ -257,7 +257,7 @@ static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) .valid_lifetime = ~0U, .pref_lifetime = ~0U, }, - .prefix = c->ip6.addr, + .prefix = c->ip6.addrs[0].addr.a6, .source_ll = { .header = { .type = OPT_SRC_L2_ADDR, @@ -466,8 +466,8 @@ void ndp_send_init_req(const struct ctx *c) .icmp6_solicited = 0, /* Reserved */ .icmp6_override = 0, /* Reserved */ }, - .target_addr = c->ip6.addr + .target_addr = c->ip6.addrs[0].addr.a6 }; debug("Sending initial NDP NS request for guest MAC address"); - ndp_send(c, &c->ip6.addr, &ns, sizeof(ns)); + ndp_send(c, &c->ip6.addrs[0].addr.a6, &ns, sizeof(ns)); } diff --git a/passt.h b/passt.h index 79d01dd..9c0c3fe 100644 --- a/passt.h +++ b/passt.h @@ -66,9 +66,9 @@ enum passt_modes {
/** * struct ip4_ctx - IPv4 execution context - * @addr: IPv4 address assigned to guest + * @addrs: IPv4 addresses assigned to guest + * @addr_count: Number of addresses in addrs[] array * @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 @@ -85,9 +85,10 @@ enum passt_modes { */ struct ip4_ctx { /* PIF_TAP addresses */ - struct in_addr addr; + struct inany_addr_entry addrs[IP4_MAX_ADDRS]; + int addr_count; + 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; @@ -107,7 +108,8 @@ struct ip4_ctx {
/** * struct ip6_ctx - IPv6 execution context - * @addr: IPv6 address assigned to guest + * @addrs: IPv6 addresses assigned to guest + * @addr_count: Number of addresses in addrs[] array * @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 @@ -126,7 +128,9 @@ struct ip4_ctx { */ struct ip6_ctx { /* PIF_TAP addresses */ - struct in6_addr addr; + struct inany_addr_entry addrs[IP6_MAX_ADDRS]; + int addr_count; + struct in6_addr addr_seen; struct in6_addr addr_ll_seen; struct in6_addr guest_gw; diff --git a/pasta.c b/pasta.c index c307b8a..1bb3dd0 100644 --- a/pasta.c +++ b/pasta.c @@ -340,8 +340,8 @@ void pasta_ns_conf(struct ctx *c) if (c->ip4.no_copy_addrs) { rc = nl_addr_set(nl_sock_ns, c->pasta_ifi, AF_INET, - &c->ip4.addr, - c->ip4.prefix_len); + inany_v4(&c->ip4.addrs[0].addr), + c->ip4.addrs[0].prefix_len); } else { rc = nl_addr_dup(nl_sock, c->ifi4, nl_sock_ns, c->pasta_ifi, @@ -387,10 +387,12 @@ void pasta_ns_conf(struct ctx *c) 0, IFF_NOARP);
if (c->ip6.no_copy_addrs) { - if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) { + struct in6_addr *a = &c->ip6.addrs[0].addr.a6; + + if (!IN6_IS_ADDR_UNSPECIFIED(a)) { rc = nl_addr_set(nl_sock_ns, - c->pasta_ifi, AF_INET6, - &c->ip6.addr, 64); + c->pasta_ifi, + AF_INET6, a, 64); } } else { rc = nl_addr_dup(nl_sock, c->ifi6, diff --git a/tap.c b/tap.c index 9d1344b..7c50013 100644 --- a/tap.c +++ b/tap.c @@ -951,8 +951,8 @@ resume: c->ip6.addr_seen = *saddr; }
- if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) - c->ip6.addr = *saddr; + if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addrs[0].addr.a6)) + c->ip6.addrs[0].addr.a6 = *saddr; } else if (!IN6_IS_ADDR_UNSPECIFIED(saddr)){ c->ip6.addr_seen = *saddr; }
-- Stefano