dev
Threads by month
- ----- 2026 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- 10 participants
- 1780 discussions
Fuzz testing with AFL++ persistent mode found a crash in
tcp_buf_data_from_sock(), SIGFPE (signal 8) from division
by zero at:
fill_bufs = DIV_ROUND_UP(wnd_scaled - already_sent, mss);
when we split buffers on MSS boundaries, if the MSS is small
enough (less than four, rounded down to 0).
A guest advertising such a small MSS indicates unusual
behaviour because the minimum usable MSS is
MTU - IP header - TCP header (28 bytes for the minimum
IPv4 MTU of 68). To improve robustness:
- Fix MSS_SET() to round up when MSS is between 1 and 3,
storing tap_mss as 1 (recovered as 4 by MSS_GET()).
- In case of zero MSS, don't send data: tcp_buf_data_from_sock()
now returns early.
This is not a security issue: the effect of a zero MSS from
a guest input was a clean process termination (SIGFPE), not
memory corruption or data leakage.
Signed-off-by: Anshu Kumari <anskuma(a)redhat.com>
---
tcp_buf.c | 3 +++
tcp_conn.h | 3 ++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/tcp_buf.c b/tcp_buf.c
index 72c45412..ee8c7ce8 100644
--- a/tcp_buf.c
+++ b/tcp_buf.c
@@ -335,6 +335,9 @@ int tcp_buf_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn,
struct iovec *iov;
uint32_t seq;
+ if (!mss)
+ return 0;
+
/* Set up buffer descriptors we'll fill completely and partially. */
fill_bufs = DIV_ROUND_UP(wnd_scaled - already_sent, mss);
if (fill_bufs > TCP_FRAMES) {
diff --git a/tcp_conn.h b/tcp_conn.h
index e3c55341..3f5ad28b 100644
--- a/tcp_conn.h
+++ b/tcp_conn.h
@@ -51,7 +51,8 @@ struct tcp_tap_conn {
#define TCP_MSS_BITS 14
unsigned int tap_mss :TCP_MSS_BITS;
-#define MSS_SET(conn, mss) (conn->tap_mss = (mss >> (16 - TCP_MSS_BITS)))
+#define MSS_SET(conn, mss) \
+ (conn->tap_mss = (mss) ? MAX(1, (mss) >> (16 - TCP_MSS_BITS)) : 0)
#define MSS_GET(conn) (conn->tap_mss << (16 - TCP_MSS_BITS))
#define RTT_EXP_BITS 4
--
2.55.0
3
4
23 Oct '26
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 <jmaloy(a)redhat.com>
---
v2: -Using inany_addr instead of protocol specific addresses as
entry address field.
v3: -Merging into one array, directly in struct ctx
-Changed prefix_len and flags fields in struct inany_addr_entry
to uint8_t, since that makes the struct directly migratable
v4: -Updated according to changes in previous commits
-Updated according to feedback from David G.
-Squashed IP4_MASK macro commit into this one
v6: -Renamed and moved some definitions
-Introduced fwd_set_addr() and fwd_get_addr() already in this commit
-Eliminated first_v4/v6() functions, replaced with fwd_get_addr()
-Some other changes as suggested by David G.
-I kept the flag CONF_ADDR_LINKLOCAL, since it will be
needed later in an address selection function.
v7: -Introduced CONF_ADDR_GENERATED flag
-Other fixes based on feedback from David and Stefano.
-I changed signature of inany_prefix_len(), but I did not change
its semantics, since the premise of David's comment is wrong: the
caller does *not* explicitly know he is dealing with an IPv4 address.
In fact, there are examples later in this series where it may be an
IPv6 address, and the caller just trusts he gets the return value in
the appropriate format.
-Intoduced the inverse of inany_prefix_len(), called inany_prefix_len6()
which always returns the prefix in IPv6 or mapped IPv4 format.
The name of the function isn't great, but any alternative I came up
with became too long to be practical.
v8: -Added inany_prefix_v4() (David G.) to extract IPv4 address and
prefix length simultaneously, replacing inany_prefix_len().
-Removed both inany_prefix_len() and inany_prefix_len6()
auto-detecting functions. Prefix length attached to an inany is
now always stored in IPv6 format (96-128 for IPv4-mapped).
-fwd_set_addr() now requires prefix_len in IPv6 format; callers
updated to pass prefix_len + 96 for IPv4 addresses.
-Fixed -n option to store prefix_len in IPv6 format.
-Fixed addr_seen regression: when address was set via -a,
addr_seen was not initialised in conf_ip4().
-Replaced assert() with graceful no-op in dhcp.c and ndp.c when
no address is configured (David G.).
-Note: addr_seen is still initialised both at conf_ip*() time
and at DHCP/DHCPv6 send time (David's concern). This
inconsistency will be addressed in a follow-up.
-Introduced a new CONF_ADDR_ANY macro to be used for address
selection, as suggested by Stefano.
-Deferred introduction of an explicit scope field (replacing
CONF_ADDR_LINKLOCAL flag) to a later commit or series.
v9: -Rebased on changes made by David Gibson.
-Some minor changes based on feedback from David.
-Defined a new macro CONF_ADDR_GLOBAL to supplant CONF_ADDR_ANY.
-I did not follow up David's suggestion to introduce three
functions to replace the current fwd_get_addr(), because I
don't think it would solve any problems. The 'flags' field
encode both provenance and type properties orthogonally, and
callers need different combinations of them. That is the very
point of that field, and I see nothing confusing in it.
It is correct that some of the calls to fwd_get_addr() go away
in later commits, but enough of them remain to justify its
existence. Defining three new functions to replace it would only
make them wrappers around fwd_get_addr(), and serve no purpose in
my view.
v10: -Redefined and refactored function fwd_get_addr().
v11: -Changes after feedback from Stefano.
- Fixed coverity false positives.
- Fixed bug introduced by wrongly handling ipv4 link local
addresses analogously to ipv6 ditto. These addresses should in
practice be handled like global IPv4 address. Removed now
redundant inany_is_linklocal() function and let fwd_get_addr()
only use inany_is_linklocal6().
- In the same function the removal of the fallback does not
imply a functional change:
* -a without a prefix still gets the class-based default from
inany_default_prefix_len()
* host-discovered addresses get their prefix_len from netlink.
- The changes in conf_print() is not unrelated: inet_ntop() may
fail and leave a stale buffer.
- Re-introduced CONF_ADDR_ANY, this time to 0
- Kept next_addr_idx_(). There is a working solution without it,
but (kernel) checkpatch complains about it.
solution for this.
- Some other minor changes
---
arp.c | 21 +++++++-
conf.c | 158 +++++++++++++++++++++++++++++++++----------------------
dhcp.c | 20 +++++--
dhcpv6.c | 15 ++++--
fwd.c | 112 ++++++++++++++++++++++++++++++++-------
fwd.h | 4 ++
inany.h | 30 +++++++++++
ip.h | 2 +
ndp.c | 19 +++++--
passt.h | 61 ++++++++++++++++++---
pasta.c | 23 +++++---
tap.c | 8 ++-
12 files changed, 364 insertions(+), 109 deletions(-)
diff --git a/arp.c b/arp.c
index bb042e95..df230d34 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 *v4;
+
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;
+ v4 = inany_v4(&a->addr);
+ if (v4 && !memcmp(am->tip, v4, 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 *v4;
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;
+
+ v4 = inany_v4(&a->addr);
+ if (!v4)
+ 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, v4, 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 149a4c79..91ebf29d 100644
--- a/conf.c
+++ b/conf.c
@@ -372,13 +372,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 *v4;
+
+ a = fwd_get_addr(c, AF_INET, true, CONF_ADDR_ANY);
+
if (!ifi)
ifi = nl_get_ext_if(nl_sock, AF_INET);
@@ -397,67 +403,76 @@ 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);
+ v4 = inany_v4(&a->addr);
+ assert(v4);
+ ip4->addr_seen = *v4;
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_GENERATED,
+ IP4_LL_PREFIX_LEN + 96);
+ ip4->addr_seen = IP4_LL_GUEST_ADDR;
+ } else {
+ const struct in_addr *v4 = inany_v4(&a->addr);
+
+ assert(v4);
+ ip4->addr_seen = *v4;
+ }
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;
@@ -478,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;
@@ -502,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;
@@ -801,6 +825,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)
@@ -848,16 +873,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)));
@@ -868,8 +896,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++) {
@@ -894,13 +922,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++) {
@@ -908,8 +937,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++) {
@@ -1213,15 +1244,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;
@@ -1331,6 +1360,7 @@ void conf(struct ctx *c, int argc, char **argv)
unsigned int ifi4 = 0, ifi6 = 0;
bool opt_a_is_prefix = false;
const char *logfile = NULL;
+ struct guest_addr *a;
char *runas = NULL;
size_t logsize = 0;
uint8_t opt_n = 0;
@@ -1652,8 +1682,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;
@@ -1853,9 +1887,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)",
@@ -1878,7 +1912,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;
}
@@ -1886,7 +1920,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;
}
@@ -1959,7 +1993,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 c3c74224..9336f504 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -303,7 +303,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;
@@ -315,6 +317,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
struct msg const *m;
struct msg reply;
unsigned int i;
+ int plen;
eh = IOV_REMOVE_HEADER(data, eh_storage);
iph = IOV_PEEK_HEADER(data, iph_storage);
@@ -344,6 +347,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;
@@ -352,7 +364,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));
@@ -408,7 +420,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);
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));
@@ -416,7 +428,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;
@@ -475,7 +487,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 29c7e320..b00160f5 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;
@@ -574,6 +575,8 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
const struct udphdr *uh;
size_t mlen, n;
+ a = fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY);
+
uh = IOV_REMOVE_HEADER(data, uh_storage);
if (!uh)
return -1;
@@ -627,7 +630,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 (a && dhcpv6_ia_notonlink(data, &a->addr.a6)) {
dhcpv6_send_ia_notonlink(c, saddr, data,
&client_id_base,
@@ -682,7 +685,8 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
resp.hdr.xid = mh->xid;
tap_udp6_send(c, src, 547, saddr, 546, mh->xid, &resp, n);
- c->ip6.addr_seen = c->ip6.addr;
+ if (a)
+ c->ip6.addr_seen = a->addr.a6;
return 1;
}
@@ -694,6 +698,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
void dhcpv6_init(const struct ctx *c)
{
time_t y2k = 946684800; /* Epoch to 2000-01-01T00:00:00Z, no mktime() */
+ const struct guest_addr *a;
uint32_t duid_time;
duid_time = htonl(difftime(time(NULL), y2k));
@@ -706,5 +711,7 @@ 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;
+ a = fwd_get_addr(c, AF_INET6, true, CONF_ADDR_ANY);
+ if (a)
+ resp.ia_addr.addr = a->addr.a6;
}
diff --git a/fwd.c b/fwd.c
index 5679a3d6..4a367099 100644
--- a/fwd.c
+++ b/fwd.c
@@ -243,6 +243,60 @@ 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;
+
+ 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 (flags && !(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 +853,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 *v4;
+
if (IN4_IS_ADDR_LOOPBACK(addr))
return false;
@@ -815,7 +872,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);
+ v4 = a ? inany_v4(&a->addr) : NULL;
+ if ((v4 && IN4_ARE_ADDR_EQUAL(addr, v4)) ||
IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr_seen))
return false;
@@ -833,10 +892,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 +943,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 +1062,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 b60697d9..d001d8ae 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 d0befb15..735d7695 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 *v4 = inany_v4(addr);
+
+ if (!v4)
+ return NULL;
+
+ *v4_plen = prefix_len - 96;
+
+ return v4;
+}
+
+/** 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
*
diff --git a/ip.h b/ip.h
index aab9b86a..ac93c543 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 1f2bcb0c..86df4798 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 afd8e9f0..8728bd43 100644
--- a/passt.h
+++ b/passt.h
@@ -64,11 +64,28 @@ 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 interface */
+#define CONF_ADDR_GENERATED BIT(2) /* Generated by PASST/PASTA */
+#define CONF_ADDR_ANY 0 /* Any address 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 +104,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 +125,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 +146,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 +205,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
@@ -276,6 +291,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;
@@ -321,6 +339,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 10f3ec8d..1410a8c3 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 *v4;
+ int plen = 0;
+
+ v4 = 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);
+ v4, 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 dfa66c71..1f2da35a 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_GENERATED, 64);
+ }
} else if (!c->ip6.addr_fixed &&
!IN6_IS_ADDR_UNSPECIFIED(saddr)) {
c->ip6.addr_seen = *saddr;
--
2.52.0
1
0
22 Oct '26
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 <jmaloy(a)redhat.com>
---
v2: -Using inany_addr instead of protocol specific addresses as
entry address field.
v3: -Merging into one array, directly in struct ctx
-Changed prefix_len and flags fields in struct inany_addr_entry
to uint8_t, since that makes the struct directly migratable
v4: -Updated according to changes in previous commits
-Updated according to feedback from David G.
-Squashed IP4_MASK macro commit into this one
v6: -Renamed and moved some definitions
-Introduced fwd_set_addr() and fwd_get_addr() already in this commit
-Eliminated first_v4/v6() functions, replaced with fwd_get_addr()
-Some other changes as suggested by David G.
-I kept the flag CONF_ADDR_LINKLOCAL, since it will be
needed later in an address selection function.
v7: -Introduced CONF_ADDR_GENERATED flag
-Other fixes based on feedback from David and Stefano.
-I changed signature of inany_prefix_len(), but I did not change
its semantics, since the premise of David's comment is wrong: the
caller does *not* explicitly know he is dealing with an IPv4 address.
In fact, there are examples later in this series where it may be an
IPv6 address, and the caller just trusts he gets the return value in
the appropriate format.
-Intoduced the inverse of inany_prefix_len(), called inany_prefix_len6()
which always returns the prefix in IPv6 or mapped IPv4 format.
The name of the function isn't great, but any alternative I came up
with became too long to be practical.
v8: -Added inany_prefix_v4() (David G.) to extract IPv4 address and
prefix length simultaneously, replacing inany_prefix_len().
-Removed both inany_prefix_len() and inany_prefix_len6()
auto-detecting functions. Prefix length attached to an inany is
now always stored in IPv6 format (96-128 for IPv4-mapped).
-fwd_set_addr() now requires prefix_len in IPv6 format; callers
updated to pass prefix_len + 96 for IPv4 addresses.
-Fixed -n option to store prefix_len in IPv6 format.
-Fixed addr_seen regression: when address was set via -a,
addr_seen was not initialised in conf_ip4().
-Replaced assert() with graceful no-op in dhcp.c and ndp.c when
no address is configured (David G.).
-Note: addr_seen is still initialised both at conf_ip*() time
and at DHCP/DHCPv6 send time (David's concern). This
inconsistency will be addressed in a follow-up.
-Introduced a new CONF_ADDR_ANY macro tobe used for address
selection, as suggested by Stefano.
-Deferred introduction of an explicit scope field (replacing
CONF_ADDR_LINKLOCAL flag) to a later commit or series.
v9: -Rebased on changes made by David Gibson.
-Some minor changes based on feedback from David.
-Defined a new macro CONF_ADDR_GLOBAL to supplant CONF_ADDR_ANY.
-I did not follow up David's suggestion to introduce three
functions to replace the current fwd_get_addr(), because I
don't think it would solve any problems. The 'flags' field
encode both provenance and type properties orthogonally, and
callers need different combinations of them. That is the very
point of that field, and I see nothing confusing in it.
It is correct that some of the calls to fwd_get_addr() go away
in later commits, but enough of them remain to justify its
existence. Defining three new functions to replace it would only
make them wrappers around fwd_get_addr(), and serve no purpose in
my view.
v10: -Redefined and refactored function fwd_get_addr().
---
arp.c | 12 ++++-
conf.c | 152 ++++++++++++++++++++++++++++++++-----------------------
dhcp.c | 20 ++++++--
dhcpv6.c | 15 ++++--
fwd.c | 111 +++++++++++++++++++++++++++++++++-------
fwd.h | 4 ++
inany.h | 42 +++++++++++++++
ip.h | 2 +
ndp.c | 19 +++++--
passt.h | 60 +++++++++++++++++++---
pasta.c | 23 ++++++---
tap.c | 8 ++-
12 files changed, 358 insertions(+), 110 deletions(-)
diff --git a/arp.c b/arp.c
index bb042e95..9ee6ae65 100644
--- a/arp.c
+++ b/arp.c
@@ -41,6 +41,8 @@
static bool ignore_arp(const struct ctx *c,
const struct arphdr *ah, const struct arpmsg *am)
{
+ const struct guest_addr *a;
+
if (ah->ar_hrd != htons(ARPHRD_ETHER) ||
ah->ar_pro != htons(ETH_P_IP) ||
ah->ar_hln != ETH_ALEN ||
@@ -54,7 +56,8 @@ 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, 0);
+ if (a && !memcmp(am->tip, inany_v4(&a->addr), sizeof(am->tip)))
return true;
return false;
@@ -123,12 +126,17 @@ int arp(const struct ctx *c, struct iov_tail *data)
*/
void arp_send_init_req(const struct ctx *c)
{
+ const struct guest_addr *a;
struct {
struct ethhdr eh;
struct arphdr ah;
struct arpmsg am;
} __attribute__((__packed__)) req;
+ a = fwd_get_addr(c, AF_INET, true, 0);
+ if (!a)
+ 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 +153,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(&a->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 149a4c79..6fbdc86d 100644
--- a/conf.c
+++ b/conf.c
@@ -372,13 +372,16 @@ 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)
{
+ const struct guest_addr *a = fwd_get_addr(c, AF_INET, true, 0);
+ struct ip4_ctx *ip4 = &c->ip4;
+
if (!ifi)
ifi = nl_get_ext_if(nl_sock, AF_INET);
@@ -397,67 +400,69 @@ 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;
+ int prefix_len;
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, 0);
+ assert(a);
+ ip4->addr_seen = *inany_v4(&a->addr);
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;
+ const struct guest_addr *a = fwd_get_addr(c, AF_INET, true, 0);
+ struct ip4_ctx *ip4 = &c->ip4;
+
+ if (!a) {
+ fwd_set_addr(c, &inany_from_v4(IP4_LL_GUEST_ADDR),
+ CONF_ADDR_GENERATED,
+ IP4_LL_PREFIX_LEN + 96);
+ ip4->addr_seen = IP4_LL_GUEST_ADDR;
+ } else {
+ ip4->addr_seen = *inany_v4(&a->addr);
+ }
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;
@@ -478,23 +483,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, 0);
+ 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;
@@ -502,10 +514,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;
@@ -801,6 +815,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)
@@ -848,16 +863,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, 0);
+ 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)));
@@ -868,8 +886,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++) {
@@ -894,13 +912,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, 0);
+ 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++) {
@@ -908,8 +927,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++) {
@@ -1213,15 +1234,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;
@@ -1648,13 +1667,20 @@ void conf(struct ctx *c, int argc, char **argv)
case 'a':
opt_a_is_prefix = conf_addr(c, optarg, opt_n);
break;
- case 'n':
+ case 'n': {
+ struct guest_addr *a;
+
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;
if (!parse_mac(&p, c->our_tap_mac) ||
@@ -1853,9 +1879,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)",
@@ -1878,7 +1904,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;
}
@@ -1886,7 +1912,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;
}
@@ -1959,7 +1985,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, 0)) {
c->no_dhcpv6 = 1;
}
diff --git a/dhcp.c b/dhcp.c
index c3c74224..17eebc4a 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -303,7 +303,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;
@@ -315,6 +317,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
struct msg const *m;
struct msg reply;
unsigned int i;
+ int plen;
eh = IOV_REMOVE_HEADER(data, eh_storage);
iph = IOV_PEEK_HEADER(data, iph_storage);
@@ -344,6 +347,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, 0);
+ 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;
@@ -352,7 +364,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));
@@ -408,7 +420,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);
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));
@@ -416,7 +428,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;
@@ -475,7 +487,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 29c7e320..5fbba7cf 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;
@@ -574,6 +575,8 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
const struct udphdr *uh;
size_t mlen, n;
+ a = fwd_get_addr(c, AF_INET6, true, 0);
+
uh = IOV_REMOVE_HEADER(data, uh_storage);
if (!uh)
return -1;
@@ -627,7 +630,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 (a && dhcpv6_ia_notonlink(data, &a->addr.a6)) {
dhcpv6_send_ia_notonlink(c, saddr, data,
&client_id_base,
@@ -682,7 +685,8 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
resp.hdr.xid = mh->xid;
tap_udp6_send(c, src, 547, saddr, 546, mh->xid, &resp, n);
- c->ip6.addr_seen = c->ip6.addr;
+ if (a)
+ c->ip6.addr_seen = a->addr.a6;
return 1;
}
@@ -694,6 +698,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
void dhcpv6_init(const struct ctx *c)
{
time_t y2k = 946684800; /* Epoch to 2000-01-01T00:00:00Z, no mktime() */
+ const struct guest_addr *a;
uint32_t duid_time;
duid_time = htonl(difftime(time(NULL), y2k));
@@ -706,5 +711,7 @@ 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;
+ a = fwd_get_addr(c, AF_INET6, true, 0);
+ if (a)
+ resp.ia_addr.addr = a->addr.a6;
}
diff --git a/fwd.c b/fwd.c
index 5679a3d6..574db1b3 100644
--- a/fwd.c
+++ b/fwd.c
@@ -243,6 +243,61 @@ 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;
+
+ 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_linklocal(&a->addr) == global)
+ continue;
+ if (flags && !(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 +854,10 @@ 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;
+
if (IN4_IS_ADDR_LOOPBACK(addr))
return false;
@@ -815,7 +872,8 @@ 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, 0);
+ if ((a && IN4_ARE_ADDR_EQUAL(addr, inany_v4(&a->addr))) ||
IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr_seen))
return false;
@@ -833,10 +891,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, 0);
+ 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 +942,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, 0);
+ *translated = ga ? ga->addr : inany_any4;
+ } else if (inany_equals6(addr, &c->ip6.map_guest_addr)) {
+ ga = fwd_get_addr(c, AF_INET6, true, 0);
+ translated->a6 = ga ? ga->addr.a6 : in6addr_any;
+ } else {
*translated = *addr;
+ }
}
/**
@@ -995,16 +1061,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, 0);
+ ga6 = fwd_get_addr(c, AF_INET6, true, 0);
+
+ 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 b60697d9..d001d8ae 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 d0befb15..4f2331ee 100644
--- a/inany.h
+++ b/inany.h
@@ -104,6 +104,34 @@ 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 *v4 = inany_v4(addr);
+
+ if (!v4)
+ return NULL;
+ *v4_plen = prefix_len - 96;
+ return v4;
+}
+
+/** 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
*
@@ -230,6 +258,20 @@ static inline bool inany_is_linklocal6(const union inany_addr *a)
return IN6_IS_ADDR_LINKLOCAL(&a->a6);
}
+/** inany_is_linklocal() - Check if address is link-local
+ * @a: IPv[46] address
+ *
+ * Return: true if @a is in 169.254.0.0/16 or fe80::/10
+ */
+static inline bool inany_is_linklocal(const union inany_addr *a)
+{
+ const struct in_addr *v4 = inany_v4(a);
+
+ if (v4)
+ return IN4_IS_ADDR_LINKLOCAL(v4);
+ return IN6_IS_ADDR_LINKLOCAL(&a->a6);
+}
+
/** inany_is_multicast() - Check if address is multicast or broadcast
* @a: IPv[46] address
*
diff --git a/ip.h b/ip.h
index aab9b86a..ac93c543 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 1f2bcb0c..ac1325e1 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, 0);
+ 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, 0);
+ 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 afd8e9f0..47e25d5c 100644
--- a/passt.h
+++ b/passt.h
@@ -64,11 +64,27 @@ 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 interface */
+#define CONF_ADDR_GENERATED BIT(2) /* Generated by PASST/PASTA */
+};
+
/**
* 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 +103,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 +124,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 +145,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 +204,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
@@ -276,6 +290,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;
@@ -321,6 +338,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 10f3ec8d..24427ddb 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, 0);
+ if (!a)
+ return 0;
+
+ if (af == AF_INET) {
+ const struct in_addr *v4;
+ int plen = 0;
+
+ v4 = 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);
+ v4, 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 dfa66c71..604b54ca 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, 0) &&
+ !fwd_get_addr(c, AF_INET6, false, 0)) {
+ union inany_addr addr = { .a6 = *saddr };
+
+ fwd_set_addr(c, &addr, CONF_ADDR_GENERATED, 64);
+ }
} else if (!c->ip6.addr_fixed &&
!IN6_IS_ADDR_UNSPECIFIED(saddr)) {
c->ip6.addr_seen = *saddr;
--
2.52.0
2
1
This work is based on the original v2 RFC by Eugenio Perez:
Link:
https://archives.passt.top/passt-dev/20250709174748.3514693-1-eperezma@redh…
It is the first finished patch series for vhost-kernel support after a
third RFC was submitted:
Link:
https://archives.passt.top/passt-dev/20260802132155.870796-1-aerosound161@g…
vhost-net is a kernel device that allows reading and writing packets to a
tap device using virtqueues instead of read(2), write(2), and their
vectored counterparts. Frames are handed over through a shared descriptor
ring, so several of them can be passed to the kernel at once rather than
issuing a syscall per frame.
The v3 RFC didn't yield any performance improvements because, to retain
correctness, sends between pasta and the vhost-kernel thread had to be
synchronous. This patch series fixes this problem through the changes
introduced in the last commit, titled:
"tap/tcp: Replace tcp_payload_used with a ring buffer style index"
The changes reliably yield a roughly 2x speedup on the TX path (container
to host).
Numbers were obtained through iperf3 with the following methodology and
results:
Pasta command:
./pasta -f --runas 0:0 --map-guest-addr 192.168.0.5
--vhost-kernel on --config-net 2729785
Host:
iperf3 -s1J
Container:
iperf3 -c 192.168.0.5 -t 10 -P4 -l 1M -w 32M -i1
With --vhost-kernel on:
[SUM] 0.00-10.00 sec 1.36 GBytes 1.17 Gbits/sec 3643 sender
[SUM] 0.00-10.00 sec 1.16 GBytes 999 Mbits/sec receiver
With --vhost-kernel off:
[SUM] 0.00-10.00 sec 700 MBytes 587 Mbits/sec 3556 sender
[SUM] 0.00-10.10 sec 495 MBytes 411 Mbits/sec receiver
v2:
- removed all use of epoll_ctl in vhost_setup_eventfds in favor of epoll_add
- prevent all functions that register memory regions into the vhost_memory
object from overflowing
- remove the unions with flexible array members in vring_avail_pasta and
vring_used_pasta in favor of hardcoding the ring array to have VHOST_NDESCS
entries to avoid having to ignore Wpedantic
- remove the && operator with TCP_CURR_IDX when computing no_csum
- make desc_id in tx_reap a uint32_t
- don't make vhost-kernel a required argument
Ammar Yasser (7):
tap: Move the tap_hdr file to a separate file
conf: Add context fields, epoll types and --vhost-kernel flag to pasta
virtio: Add the pasta vhost-net interface and implementation
tap: Implement the pasta vhost-net from-guest path
tap, tcp, udp: Prepare the to-guest path for vhost-net
tap: Implement the pasta vhost-net to-guest path
tap/tcp: Replace tcp_payload_used with a ring buffer style index
Makefile | 2 +-
conf.c | 19 +++
epoll_type.h | 4 +
passt.c | 9 ++
passt.h | 79 ++++++++++-
tap.c | 368 +++++++++++++++++++++++++++++++++++++++++++++------
tap.h | 47 ++-----
tap_hdr.h | 23 ++++
tcp_buf.c | 149 ++++++++++++++++-----
tcp_buf.h | 4 +
udp.c | 47 +++++--
udp.h | 3 +
util.c | 18 +++
util.h | 2 +
vhost.c | 295 +++++++++++++++++++++++++++++++++++++++++
vhost.h | 91 +++++++++++++
16 files changed, 1036 insertions(+), 124 deletions(-)
create mode 100644 tap_hdr.h
create mode 100644 vhost.c
create mode 100644 vhost.h
--
2.39.5 (Apple Git-154)
2
1
When pasta mode is used to launch an executable (`pasta [COMMAND]`) and
that executable accepts inputs in the form of arbitrary file descriptors
(such as `bwrap`), then passt should not stand in the way of the parent
process handing off those file descriptors to the child process.
See bug 204 for additional discussion.
The strategy used here is to delay the closing of inherited file
descriptors until after pasta has forked to execute the COMMAND,
which happens during the call to `conf()`.
A nice side-effect is that the `--fd` argument no longer needs to be
parsed early, and can rejoin the other args parsed normally in `conf()`.
Highlighted changes:
- `conf_tap_fd()` is now called directly by `conf()`.
- The tap fd is relocated to a number at least 3, rather than exactly 3.
- `snapshot_initial_fds()` memorizes inherited fds early in startup.
- Inherited fds are discovered by reading from `/proc` if available.
- `isolate_fds()` is now called after `conf()` in `main()`.
Signed-off-by: Richard Lawrence <rlawrence(a)tamu.edu>
---
conf.c | 39 ++++++++-------
conf.h | 1 -
isolation.c | 135 ++++++++++++++++++++++++++++++++++++----------------
isolation.h | 9 +++-
passt.c | 7 ++-
5 files changed, 129 insertions(+), 62 deletions(-)
diff --git a/conf.c b/conf.c
index faf2681..6bfdfb6 100644
--- a/conf.c
+++ b/conf.c
@@ -1169,26 +1169,20 @@ static void conf_sock_listen(const struct ctx *c)
}
/**
- * conf_tap_fd() - Read tap fd as supplied by -F command line option
- * @argc: Argument count
- * @argv: Command line options
+ * conf_tap_fd() - Read and relocate tap fd as supplied by -F command line option
+ * @fdarg: String containing fd
+ *
+ * Should:
+ * - move the --fd descriptor out of the range 0-2
*
* Return: fd number from --fd option, or -1 if not supplied
*/
-int conf_tap_fd(int argc, char **argv)
+int conf_tap_fd(const char *fdarg)
{
- const struct option optfd[] = { { "fd", required_argument, NULL, 'F' },
- { 0 }, };
- const char *fdarg = NULL, *p;
unsigned long val;
- int name;
-
- optind = 0;
- do {
- name = getopt_long(argc, argv, "-:F:", optfd, NULL);
- if (name == 'F')
- fdarg = optarg;
- } while (name != -1);
+ const char *p;
+ int new_fd;
+ int fd;
if (!fdarg)
return -1;
@@ -1197,7 +1191,18 @@ int conf_tap_fd(int argc, char **argv)
if (!parse_unsigned(&p, 0, &val) || !parse_eoi(p) || val > INT_MAX)
die("Invalid --fd: %s", fdarg);
- return val;
+ fd = (int)val;
+ if (fd >= 0 && fd < 3) {
+ new_fd = fcntl(fd, F_DUPFD, 3);
+
+ if (new_fd < 0)
+ die_perror("Could not relocate --fd descriptor");
+
+ close(fd);
+ fd = new_fd;
+ }
+
+ return fd;
}
/**
@@ -1625,7 +1630,7 @@ void conf(struct ctx *c, int argc, char **argv)
c->fd_control_listen = c->fd_control = -1;
break;
case 'F':
- /* --fd was parsed early and c->fd_tap set in main() */
+ c->fd_tap = conf_tap_fd(optarg);
c->one_off = true;
*c->sock_path = 0;
break;
diff --git a/conf.h b/conf.h
index 19bf9bc..16f9718 100644
--- a/conf.h
+++ b/conf.h
@@ -7,7 +7,6 @@
#define CONF_H
enum passt_modes conf_mode(int argc, char *argv[]);
-int conf_tap_fd(int argc, char **argv);
void conf(struct ctx *c, int argc, char **argv);
void conf_listen_handler(struct ctx *c, uint32_t events);
void conf_handler(struct ctx *c, uint32_t events);
diff --git a/isolation.c b/isolation.c
index 94cbe7f..2434748 100644
--- a/isolation.c
+++ b/isolation.c
@@ -24,19 +24,13 @@
* done anything we need to do with those resources, so we have
* multiple stages of self-isolation. In order these are:
*
- * 1a. isolate_initial()
+ * 1. isolate_initial()
* ====================
*
* Executed immediately after startup, drops capabilities we don't
* need at any point during execution (or which we gain back when we
* need by joining other namespaces).
*
- * 1b. isolate_fds()
- * ================
- *
- * Executed immediately after isolate_initial(). Closes any leaked
- * files we might have inherited from the parent process.
- *
* 2. isolate_user()
* =================
*
@@ -44,14 +38,20 @@
* operate in. Sets our final UID & GID, and enters the correct user
* namespace.
*
- * 3. isolate_prefork()
+ * 3. isolate_fds()
+ * ================
+ *
+ * Executed after conf(). Closes any leaked
+ * files we might have inherited from the parent process.
+ *
+ * 4. isolate_prefork()
* ====================
*
* Executed after all setup, but before daemonising (fork()ing into
* the background). Uses mount namespace and pivot_root() to remove
* our access to the filesystem.
*
- * 4. isolate_postfork()
+ * 5. isolate_postfork()
* =====================
*
* Executed immediately after daemonizing, but before entering the
@@ -61,6 +61,7 @@
* runtime operation.
*/
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
@@ -77,6 +78,7 @@
#include <unistd.h>
#include <sys/mount.h>
#include <sys/prctl.h>
+#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
@@ -248,47 +250,96 @@ void isolate_initial(void)
drop_caps_ep_except(keep);
}
-/*
- * isolate_fds() - Close leaked files, but not --fd, stdin, stdout, stderr
- * @argc: Argument count
- * @argv: Command line options, as we need to skip any file given via --fd
- *
- * Should:
- * - close all open files except for standard streams and the one from --fd
- * - move the --fd descriptor out of the range 0-2
- *
- * Return: new fd number for descriptor from --fd, or -1 if not specified
+/**
+ * snapshot_initial_fds() - Snapshot initial file descriptors inherited from parent
+ * other than standard streams (stdin, stdout, stderr)
+ * @ifds: Snapshot struct of initial file descriptors to populate
*/
-int isolate_fds(int argc, char **argv)
+void snapshot_initial_fds(struct initial_fd_snapshot *ifds)
{
- int fd, close_from = STDERR_FILENO + 1;
+ struct dirent *entry;
+ size_t capacity = 16;
+ int max_fd = 1024;
+ struct rlimit rl;
+ long parsed_fd;
+ char *endptr;
+ int dir_fd;
+ DIR *dir;
+ int fd;
+
+ ifds->arr = NULL;
+ ifds->count = 0;
+
+ dir = opendir("/proc/self/fd");
+ if (dir) {
+ dir_fd = dirfd(dir);
+ ifds->arr = malloc(sizeof(int) * capacity);
+ if (!ifds->arr)
+ die_perror("Failed to allocate memory for inherited fds");
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_name[0] < '0' || entry->d_name[0] > '9')
+ continue;
+ parsed_fd = strtol(entry->d_name, &endptr, 10);
+ if (*endptr != '\0' || parsed_fd < 0 || parsed_fd > INT_MAX)
+ continue;
+ fd = (int)parsed_fd;
+ /* Ignore stdin/stdout/stderr and the opendir handle itself */
+ if (fd > STDERR_FILENO && fd != dir_fd) {
+ if (ifds->count >= capacity) {
+ size_t new_cap = capacity * 2;
+ int *new_arr = realloc(ifds->arr, sizeof(int) * new_cap);
+
+ if (!new_arr)
+ die_perror("Failed to reallocate memory for inherited fds");
+
+ ifds->arr = new_arr;
+ capacity = new_cap;
+ }
+ ifds->arr[ifds->count++] = fd;
+ }
+ }
+ closedir(dir);
+ return;
+ }
+
+ /* Fallback for environments without /proc (e.g. minimal chroots) */
+ if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_cur != RLIM_INFINITY)
+ max_fd = (int)rl.rlim_cur;
- fd = conf_tap_fd(argc, argv);
+ ifds->arr = malloc(sizeof(int) * max_fd);
+ if (!ifds->arr)
+ die_perror("Failed to allocate memory for inherited fds fallback");
- if (fd >= 0) {
- /* Move the passed fd to a more convenient location */
- if (fd != close_from &&
- (dup2(fd, close_from) != close_from ||
- close(fd)))
- die_perror("Could not move --fd descriptor");
- fd = close_from++;
+ for (fd = STDERR_FILENO + 1; fd < max_fd; fd++) {
+ if (fcntl(fd, F_GETFD) >= 0)
+ ifds->arr[ifds->count++] = fd;
}
+}
- if (close_range(close_from, ~0U, CLOSE_RANGE_UNSHARE)) {
- if (errno == ENOSYS || errno == EINVAL) {
- /* This probably means close_range() or the
- * CLOSE_RANGE_UNSHARE flag is not supported by the
- * kernel. Not much we can do here except carry on and
- * hope for the best.
- */
- warn(
-"Can't use close_range() to ensure no files leaked by parent");
- } else {
- die_perror("Failed to close files leaked by parent");
- }
+/**
+ * isolate_fds() - Close leaked files from the parent process
+ * @ifds: Snapshot of initial file descriptors
+ * @keep_fd: File descriptor to keep open, if any
+ *
+ * Should:
+ * - close all file descriptors that were open at startup, except for keep_fd
+ */
+void isolate_fds(struct initial_fd_snapshot *ifds, int keep_fd)
+{
+ size_t i;
+
+ if (!ifds || !ifds->arr)
+ return;
+
+ for (i = 0; i < ifds->count; i++) {
+ if (ifds->arr[i] != keep_fd)
+ close(ifds->arr[i]);
}
- return fd;
+ free(ifds->arr);
+ ifds->arr = NULL;
+ ifds->count = 0;
}
/**
diff --git a/isolation.h b/isolation.h
index ec47038..8ada988 100644
--- a/isolation.h
+++ b/isolation.h
@@ -8,10 +8,17 @@
#define ISOLATION_H
#include <stdbool.h>
+#include <stddef.h>
#include <unistd.h>
+struct initial_fd_snapshot {
+ int *arr;
+ size_t count;
+};
+
void isolate_initial(void);
-int isolate_fds(int argc, char **argv);
+void snapshot_initial_fds(struct initial_fd_snapshot *ifds);
+void isolate_fds(struct initial_fd_snapshot *ifds, int keep_fd);
void isolate_user(const struct ctx *c, uid_t uid, gid_t gid, bool use_userns,
const char *userns);
int isolate_prefork(const struct ctx *c);
diff --git a/passt.c b/passt.c
index 5054551..c4335bf 100644
--- a/passt.c
+++ b/passt.c
@@ -335,6 +335,7 @@ int main(int argc, char **argv)
struct epoll_event events[NUM_EPOLL_EVENTS];
int nfds, devnull_fd = -1, fd;
struct ctx *c = &passt_ctx;
+ struct initial_fd_snapshot ifds;
struct rlimit limit;
struct timespec now;
struct sigaction sa;
@@ -344,8 +345,9 @@ int main(int argc, char **argv)
arch_avx2_exec(argv);
+ snapshot_initial_fds(&ifds);
+
isolate_initial();
- c->fd_tap = isolate_fds(argc, argv);
if ((devnull_fd = open("/dev/null", O_RDWR | O_CLOEXEC)) < 0)
die_perror("Failed to open /dev/null");
@@ -390,6 +392,9 @@ int main(int argc, char **argv)
sock_probe_features(c);
conf(c, argc, argv);
+
+ isolate_fds(&ifds, c->fd_tap);
+
trace_init(c->trace);
pasta_netns_quit_init(c);
--
2.52.0
4
8
21 Oct '26
This patch adds a `nl_collect` function to store all datagram
chunks of a multipart route dump in one single interim buffer
for further processing and dependency resolution.
---
netlink.c | 79 +++++++++++++++++++++++++++++++++++++++----------------
1 file changed, 56 insertions(+), 23 deletions(-)
diff --git a/netlink.c b/netlink.c
index 225b666..650a6fd 100644
--- a/netlink.c
+++ b/netlink.c
@@ -229,6 +229,51 @@ static struct nlmsghdr *nl_next(int s, char *buf, struct nlmsghdr *nh, ssize_t *
/* NOLINTNEXTLINE(readability-inconsistent-ifelse-braces) */\
} else
+/**
+ * nl_collect() - Collect all respose Datagrams of a netlink multi part dump
+ * @s: Netlink socket
+ * @buf: Buffer for responses (at least NLBUFSIZ long)
+ * @buflen: Buffer size.
+ * @seq: Sequence number of request we're getting responses for
+ *
+ * Return: Length of collected responses in bytes or error code < 0
+ */
+int nl_collect(int s, char *buf, size_t buflen, uint32_t seq)
+{
+ size_t collected = 0;
+ struct nlmsghdr *nh;
+ ssize_t n;
+ int res;
+ const struct nlmsghdr* tail;
+
+ #define NLMSG_DONE_SIZE NLMSG_LENGTH(4)
+
+ while(collected + NLBUFSIZ < buflen){
+ nh = nl_next(s, &buf[collected], NULL, &n);
+ debug("collect: add chunk of size n=%ld at offset=%ld, seq=%d nlmsg_len=%d",
+ n, collected, seq, ((const struct nlmsghdr *)&buf[collected])->nlmsg_len);
+ res = nl_status(nh, n, seq);
+ if (res < 0) /* error */
+ return res;
+ collected += n;
+
+ /* look for NLMSG_DONE -- it's ussualy transmitted in a seperate datagram,
+ * but in some rare cases also placed at the end of a multipart chunk.*/
+ tail = (const struct nlmsghdr*)&buf[collected - NLMSG_DONE_SIZE];
+ if (res == 0 || (
+ collected >= NLMSG_DONE_SIZE
+ && (tail = (const struct nlmsghdr*)&buf[collected - NLMSG_DONE_SIZE])
+ && NLMSG_OK(tail, NLMSG_DONE_SIZE)
+ && (nl_status(tail, NLMSG_DONE_SIZE, seq) == 0))
+ ) {
+ debug("collected: %ld", collected);
+ return collected;
+ }
+ }
+ err("netlink: Too many routes to duplicate");
+ return -E2BIG;
+}
+
/**
* nl_do() - Send netlink "do" request, and wait for acknowledgement
* @s: Netlink socket
@@ -553,21 +598,21 @@ int nl_route_dup(int s_src, unsigned int ifi_src,
.rta.rta_len = RTA_LENGTH(sizeof(unsigned int)),
.ifi = ifi_src,
};
- ssize_t nlmsgs_size, left, status;
+ ssize_t nlmsgs_size, left, status = 0;
unsigned dup_routes = 0;
struct nlmsghdr *nh;
- char buf[NLBUFSIZ];
+ char buf[NLBUFSIZ * 8];
uint32_t seq;
unsigned i;
seq = nl_send(s_src, &req, RTM_GETROUTE, NLM_F_DUMP, sizeof(req));
- /* nl_foreach() will step through multiple response datagrams,
- * which we don't want here because we need to have all the
- * routes in the buffer at once.
- */
- nh = nl_next(s_src, buf, NULL, &nlmsgs_size);
- for (left = nlmsgs_size;
+ /* collect all the chunks of a multi part dump in one buffer.*/
+ nlmsgs_size = nl_collect(s_src, buf, sizeof(buf), seq);
+ if (nlmsgs_size < 0)
+ return nlmsgs_size;
+
+ for (nh = (struct nlmsghdr *)buf, left = nlmsgs_size;
NLMSG_OK(nh, left) && (status = nl_status(nh, left, seq)) > 0;
nh = NLMSG_NEXT(nh, left)) {
struct rtmsg *rtm = (struct rtmsg *)NLMSG_DATA(nh);
@@ -648,24 +693,12 @@ int nl_route_dup(int s_src, unsigned int ifi_src,
dup_routes++;
}
- if (!NLMSG_OK(nh, left)) {
- /* Process any remaining datagrams in a different
- * buffer so we don't overwrite the first one.
- */
- char tail[NLBUFSIZ];
- unsigned extra = 0;
-
- nl_foreach_oftype(nh, status, s_src, tail, seq, RTM_NEWROUTE)
- extra++;
-
- if (extra) {
- err("netlink: Too many routes to duplicate");
- return -E2BIG;
- }
- }
+ /* status should be 0 (=NLMSG_DONE) */
if (status < 0)
return status;
+ debug("found %d routes in dump", dup_routes);
+
/* Routes might have dependencies between each other, and the kernel
* processes RTM_NEWROUTE messages sequentially. For n routes, we might
* need to send the requests up to n times to get all of them inserted.
--
2.53.0
2
3
Fixes: #221
Eliminate the route entries capacity limitations of nl_route_dup
by copping them to the destination namespace in three successive
steps to bypass dependency issues. This approach is taken from
the handling used in the restore subcommand of iproute2.
Signed-off-by: Martin Schitter <ms+git(a)mur.at>
---
netlink.c | 207 ++++++++++++++++++++++++++----------------------------
1 file changed, 99 insertions(+), 108 deletions(-)
diff --git a/netlink.c b/netlink.c
index 225b666..803cb20 100644
--- a/netlink.c
+++ b/netlink.c
@@ -525,6 +525,20 @@ int nl_route_set_def(int s, unsigned int ifi, sa_family_t af, const void *gw)
return nl_do(s, &req, RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, len);
}
+/**
+ * nl_rtattr_cmp() -- compare two routing attributes
+ * @rta1: Routing attribute
+ * @rta2: Routing attribute
+ *
+ * Return: 0 if equal
+ */
+static int nl_rtattr_cmp(const struct rtattr *rta1, const struct rtattr *rta2)
+{
+ if (!rta1 || !rta2 || rta1->rta_len != rta2->rta_len)
+ return 1;
+ return memcmp(RTA_DATA(rta1), RTA_DATA(rta2), RTA_PAYLOAD(rta1));
+}
+
/**
* nl_route_dup() - Copy routes for given interface and address family
* @s_src: Netlink socket in source namespace
@@ -535,56 +549,73 @@ int nl_route_set_def(int s, unsigned int ifi, sa_family_t af, const void *gw)
*
* Return: 0 on success, negative error code on failure
*/
-int nl_route_dup(int s_src, unsigned int ifi_src,
- int s_dst, unsigned int ifi_dst, sa_family_t af)
+int nl_route_dup(int s_src, unsigned int ifi_src, int s_dst,
+ unsigned int ifi_dst, sa_family_t af)
{
- struct req_t {
- struct nlmsghdr nlh;
- struct rtmsg rtm;
- struct rtattr rta;
- unsigned int ifi;
- } req = {
- .rtm.rtm_family = af,
- .rtm.rtm_table = RT_TABLE_MAIN,
- .rtm.rtm_scope = RT_SCOPE_UNIVERSE,
- .rtm.rtm_type = RTN_UNICAST,
+ /* Duplicate routes in correct order using 3 succesive stages:
+ * 0. ones for local addresses,
+ * 1. ones for local networks,
+ * 2. others (remote networks/hosts).
+ */
+ for (int prio = 0; prio < 3; prio++) {
+ struct req_t {
+ struct nlmsghdr nlh;
+ struct rtmsg rtm;
+ struct rtattr rta;
+ unsigned int ifi;
+ } req = {
+ .rtm.rtm_family = af,
+ .rtm.rtm_table = RT_TABLE_MAIN,
+ .rtm.rtm_scope = RT_SCOPE_UNIVERSE,
+ .rtm.rtm_type = RTN_UNICAST,
+
+ .rta.rta_type = RTA_OIF,
+ .rta.rta_len = RTA_LENGTH(sizeof(unsigned int)),
+ .ifi = ifi_src,
+ };
+ ssize_t status;
+ struct nlmsghdr *nh;
+ char buf[NLBUFSIZ];
+ uint32_t seq;
+
+ seq = nl_send(s_src, &req, RTM_GETROUTE, NLM_F_DUMP,
+ sizeof(req));
+
+ nl_foreach(nh, status, s_src, buf, seq) {
+ struct rtmsg *rtm = (struct rtmsg *)NLMSG_DATA(nh);
+ struct rtattr *rta;
+ size_t na;
+ uint16_t flags = nh->nlmsg_flags;
+ int rc;
- .rta.rta_type = RTA_OIF,
- .rta.rta_len = RTA_LENGTH(sizeof(unsigned int)),
- .ifi = ifi_src,
- };
- ssize_t nlmsgs_size, left, status;
- unsigned dup_routes = 0;
- struct nlmsghdr *nh;
- char buf[NLBUFSIZ];
- uint32_t seq;
- unsigned i;
- seq = nl_send(s_src, &req, RTM_GETROUTE, NLM_F_DUMP, sizeof(req));
+ if (nh->nlmsg_type != RTM_NEWROUTE)
+ continue;
- /* nl_foreach() will step through multiple response datagrams,
- * which we don't want here because we need to have all the
- * routes in the buffer at once.
- */
- nh = nl_next(s_src, buf, NULL, &nlmsgs_size);
- for (left = nlmsgs_size;
- NLMSG_OK(nh, left) && (status = nl_status(nh, left, seq)) > 0;
- nh = NLMSG_NEXT(nh, left)) {
- struct rtmsg *rtm = (struct rtmsg *)NLMSG_DATA(nh);
- bool discard = false;
- struct rtattr *rta;
- size_t na;
+ struct rtattr *tb[RTA_MAX+1] = { 0 };
- if (nh->nlmsg_type != RTM_NEWROUTE)
- continue;
+ for (rta = RTM_RTA(rtm), na = RTM_PAYLOAD(nh);
+ RTA_OK(rta, na); rta = RTA_NEXT(rta, na)) {
+ tb[rta->rta_type] = rta;
+ }
- /* nexthop state flags don't apply to freshly created routes,
- * and the kernel will refuse our route if they are set.
- */
- rtm->rtm_flags &= ~RTNH_COMPARE_MASK;
+ /* Process only matching routes of current priority
+ * (variant of an approach used in iproute2s restore_handler)
+ */
+ if (!(prio == 2 && tb[RTA_GATEWAY]) &&
+ !(prio == 1 && !tb[RTA_GATEWAY] &&
+ tb[RTA_PREFSRC] &&
+ nl_rtattr_cmp(tb[RTA_PREFSRC], tb[RTA_DST])) &&
+ !(prio == 0 && !tb[RTA_GATEWAY] &&
+ (!tb[RTA_PREFSRC] ||
+ !nl_rtattr_cmp(tb[RTA_PREFSRC], tb[RTA_DST]))))
+ continue;
+
+ /* nexthop state flags don't apply to freshly created routes,
+ * and the kernel will refuse our route if they are set.
+ */
+ rtm->rtm_flags &= ~RTNH_COMPARE_MASK;
- for (rta = RTM_RTA(rtm), na = RTM_PAYLOAD(nh); RTA_OK(rta, na);
- rta = RTA_NEXT(rta, na)) {
/* RTA_OIF and RTA_MULTIPATH attributes carry the
* identifier of a host interface. If they match the
* host interface we're copying from, change them to
@@ -595,103 +626,63 @@ int nl_route_dup(int s_src, unsigned int ifi_src,
* available), or if any interface index in nexthop
* objects differ from the host interface, discard the
* route altogether.
- */
- if (rta->rta_type == RTA_OIF) {
- if (*(unsigned int *)RTA_DATA(rta) != ifi_src) {
- discard = true;
- break;
+ */
+ if (tb[RTA_OIF]) {
+ if (*(unsigned int *)RTA_DATA(tb[RTA_OIF]) !=
+ ifi_src) {
+ continue;
}
-
- *(unsigned int *)RTA_DATA(rta) = ifi_dst;
- } else if (rta->rta_type == RTA_MULTIPATH) {
- int nh_len = RTA_PAYLOAD(rta);
+ *(unsigned int *)RTA_DATA(tb[RTA_OIF]) =
+ ifi_dst;
+ } else if (tb[RTA_MULTIPATH]) {
+ int nh_len = RTA_PAYLOAD(tb[RTA_MULTIPATH]);
struct rtnexthop *rtnh;
-
- for (rtnh = (struct rtnexthop *)RTA_DATA(rta);
+ bool discard = false;
+ for (rtnh = (struct rtnexthop *)
+ RTA_DATA(tb[RTA_MULTIPATH]);
RTNH_OK(rtnh, nh_len);
- rtnh = RTNH_NEXT_AND_DEC(rtnh, nh_len)) {
+ rtnh = RTNH_NEXT_AND_DEC(rtnh,
+ nh_len)) {
int src = (int)ifi_src;
-
if (rtnh->rtnh_ifindex != src) {
discard = true;
break;
}
-
rtnh->rtnh_ifindex = ifi_dst;
}
-
if (discard)
- break;
- } else if (rta->rta_type == RTA_PREFSRC ||
- rta->rta_type == RTA_NH_ID) {
+ continue;
+ } else if (tb[RTA_PREFSRC]) {
/* Strip RTA_PREFSRC attributes: host routes
* might include a preferred source address,
* which must be one of the host's addresses.
* However, with -a, pasta will use a different
* namespace address, making such a route
* invalid in the namespace.
- *
- * Strip RTA_NH_ID attributes: host routes set
+ */
+ tb[RTA_PREFSRC]->rta_type = RTA_UNSPEC;
+ } else if (tb[RTA_NH_ID]) {
+ /* Strip RTA_NH_ID attributes: host routes set
* up via routing protocols (e.g. OSPF) might
* contain a nexthop ID (and not nexthop
* objects, which are taken care of in the
* RTA_MULTIPATH case above) that's not valid
* in the target namespace.
*/
- rta->rta_type = RTA_UNSPEC;
+ tb[RTA_NH_ID]->rta_type = RTA_UNSPEC;
}
- }
-
- if (discard)
- nh->nlmsg_type = NLMSG_NOOP;
- else
- dup_routes++;
- }
-
- if (!NLMSG_OK(nh, left)) {
- /* Process any remaining datagrams in a different
- * buffer so we don't overwrite the first one.
- */
- char tail[NLBUFSIZ];
- unsigned extra = 0;
-
- nl_foreach_oftype(nh, status, s_src, tail, seq, RTM_NEWROUTE)
- extra++;
-
- if (extra) {
- err("netlink: Too many routes to duplicate");
- return -E2BIG;
- }
- }
- if (status < 0)
- return status;
-
- /* Routes might have dependencies between each other, and the kernel
- * processes RTM_NEWROUTE messages sequentially. For n routes, we might
- * need to send the requests up to n times to get all of them inserted.
- * Routes that have been already inserted will return -EEXIST, but we
- * can safely ignore that and repeat the requests. This avoids the need
- * to calculate dependencies: let the kernel do that.
- */
- for (i = 0; i < dup_routes; i++) {
- for (nh = (struct nlmsghdr *)buf, left = nlmsgs_size;
- NLMSG_OK(nh, left);
- nh = NLMSG_NEXT(nh, left)) {
- uint16_t flags = nh->nlmsg_flags;
- int rc;
-
- if (nh->nlmsg_type != RTM_NEWROUTE)
- continue;
rc = nl_do(s_dst, nh, RTM_NEWROUTE,
- (flags & ~NLM_F_DUMP_FILTERED) | NLM_F_CREATE,
+ (flags & ~NLM_F_DUMP_FILTERED) |
+ NLM_F_CREATE,
nh->nlmsg_len);
if (rc < 0 && rc != -EEXIST &&
rc != -ENETUNREACH && rc != -EHOSTUNREACH)
return rc;
}
+ if (status < 0)
+ return status;
}
-
return 0;
}
--
2.53.0
2
1
[PATCH] fwd: Keep listening on remaining rules if one of them fails to bind
by Hayato Kiwata 18 Oct '26
by Hayato Kiwata 18 Oct '26
18 Oct '26
fwd_listen_sync_() breaks out of the loop as soon as fwd_sync_one()
fails. So, if a single rule fails to bind(), for example because the
address isn't assigned to the host, no rule after it in the table is
even tried, and those forwards don't work.
Reproducer:
$ pasta --ns-ifname=tap0 --mtu=65520 --config-net --address=10.0.2.100 \
--netmask=24 --gateway=10.0.2.2 --dns-forward=10.0.2.3 --no-map-gw \
--tcp-ns=none --udp-ns=none --ipv4-only --tcp-ports=none \
--udp-ports=none --conf-path=/tmp/.pasta.sock \
-- python3 -m http.server 80
UNIX domain socket bound at /tmp/.pasta.sock
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
$ pesto -A --tcp-ports=192.0.2.1/8080:80 /tmp/.pasta.sock
$ pesto -A --tcp-ports=127.0.0.1/8085:80 /tmp/.pasta.sock
$ pesto -s /tmp/.pasta.sock
passt/pasta configuration (/tmp/.pasta.sock)
HOST
TCP [192.0.2.1]:8080 => 80
TCP [127.0.0.1]:8085 => 80
SPLICE
$ curl -sS -o /dev/null localhost:8085
curl: (7) Failed to connect to localhost port 8085 after 0 ms: Could not connect to server
pasta only reports the failure for port 8080, and never tries to
listen on port 8085:
Listen failed for HOST TCP port 192.0.2.1/8080: Cannot assign requested address
Couldn't listen on requested ports
Error switching to new forwarding rules
Keep going through the remaining rules if fwd_sync_one() fails, and
still return -1 if any of them failed, as before.
Signed-off-by: Hayato Kiwata <dev(a)haytok.jp>
---
fwd.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/fwd.c b/fwd.c
index 5679a3d..6c9df01 100644
--- a/fwd.c
+++ b/fwd.c
@@ -456,9 +456,8 @@ static int fwd_listen_sync_(void *arg)
ns_enter(a->c);
for (i = 0; i < a->c->fwd[a->pif]->count; i++) {
- a->ret = fwd_sync_one(a->c, a->pif, i, a->tcpmap, a->udpmap);
- if (a->ret < 0)
- break;
+ if (fwd_sync_one(a->c, a->pif, i, a->tcpmap, a->udpmap) < 0)
+ a->ret = -1;
}
return 0;
--
2.50.1 (Apple Git-155)
1
0
From: Ammar Yasser <aerosound161(a)gmail.com>
This work is based on the original v2 RFC by Eugenio Perez:
Link:
https://archives.passt.top/passt-dev/20250709174748.3514693-1-eperezma@redh…
It is the first finished patch series for vhost-kernel support after a
third RFC was submitted:
Link:
https://archives.passt.top/passt-dev/20260802132155.870796-1-aerosound161@g…
vhost-net is a kernel device that allows reading and writing packets to a
tap device using virtqueues instead of read(2), write(2), and their
vectored counterparts. Frames are handed over through a shared descriptor
ring, so several of them can be passed to the kernel at once rather than
issuing a syscall per frame.
The changes reliably yield a roughly 2x speedup on the TX path (container
to host).
Numbers were obtained through iperf3 with the following methodology and
results:
Pasta command:
./pasta -f --runas 0:0 --map-guest-addr 192.168.0.5
--vhost-kernel on --config-net 2729785
Host:
iperf3 -s1J
Container:
iperf3 -c 192.168.0.5 -t 10 -P4 -l 1M -w 32M -i1
With --vhost-kernel on:
[SUM] 0.00-10.00 sec 1.36 GBytes 1.17 Gbits/sec 3643 sender
[SUM] 0.00-10.00 sec 1.16 GBytes 999 Mbits/sec receiver
With --vhost-kernel off:
[SUM] 0.00-10.00 sec 700 MBytes 587 Mbits/sec 3556 sender
[SUM] 0.00-10.10 sec 495 MBytes 411 Mbits/sec receiver
v2:
- removed all use of epoll_ctl in vhost_setup_eventfds in favor of epoll_add
- prevent all functions that register memory regions into the vhost_memory
object from overflowing
- remove the unions with flexible array members in vring_avail_pasta and
vring_used_pasta in favor of hardcoding the ring array to have VHOST_NDESCS
entries to avoid having to ignore Wpedantic
- remove the && operator with TCP_CURR_IDX when computing no_csum
- make desc_id in tx_reap a uint32_t
- don't make vhost-kernel a required argument
Ammar Yasser (7):
tap: Move the tap_hdr file to a separate file
conf: Add context fields, epoll types and --vhost-kernel flag to pasta
virtio: Add the pasta vhost-net interface and implementation
tap: Implement the pasta vhost-net from-guest path
tap, tcp, udp: Prepare the to-guest path for vhost-net
tap: Implement the pasta vhost-net to-guest path
tap/tcp: Replace tcp_payload_used with a ring buffer style index
Makefile | 2 +-
conf.c | 19 +++
epoll_type.h | 4 +
passt.c | 9 ++
passt.h | 79 ++++++++++-
tap.c | 368 +++++++++++++++++++++++++++++++++++++++++++++------
tap.h | 47 ++-----
tap_hdr.h | 23 ++++
tcp_buf.c | 149 ++++++++++++++++-----
tcp_buf.h | 4 +
udp.c | 47 +++++--
udp.h | 3 +
util.c | 18 +++
util.h | 2 +
vhost.c | 295 +++++++++++++++++++++++++++++++++++++++++
vhost.h | 91 +++++++++++++
16 files changed, 1036 insertions(+), 124 deletions(-)
create mode 100644 tap_hdr.h
create mode 100644 vhost.c
create mode 100644 vhost.h
--
2.39.5 (Apple Git-154)
1
1
[PATCH v2 7/7] tap/tcp: Replace tcp_payload_used with a ring buffer style index
by aerosouund 16 Oct '26
by aerosouund 16 Oct '26
16 Oct '26
From: Ammar Yasser <aerosound161(a)gmail.com>
Because pasta resets tcp_payload_used to 0 after every tcp flush this
means that pasta will very quickly override data in the tcp buffers
after sending. This is not a problem when using writev because the data
will have been copied elsewhere by the kernel, but this is a problem for
vhost where the kernel thread may not have finished processing this
data. This is why a synchronous send was required in vhost, which
obviously affects performance.
Replace tcp_payload_used with a struct tcp_payload_idx holding the last
index we wrote to and a counter that gets updated when we flush packets,
from where we last flushed up to that index. Both are grow only and
access gets modded by TCP_FRAMES_MEM.
Rewrite functions in tcp_buf.c to use that index and handle the
wraparound case (sending a batch that started near the end of the
buffers and continued to be at the start) in tcp_payload_flush().
With the buffers no longer reused from under the kernel, drop the
synchronous wait from tap_send_frames_vhost() and reclaim descriptors
until there are enough for the send instead.
Signed-off-by: Eugenio Pérez <eperezma(a)redhat.com>
Signed-off-by: Ammar Yasser <aerosound161(a)gmail.com>
---
tap.c | 6 +--
tcp_buf.c | 115 ++++++++++++++++++++++++++++++++++++++++--------------
2 files changed, 86 insertions(+), 35 deletions(-)
diff --git a/tap.c b/tap.c
index bc2700f..0456589 100644
--- a/tap.c
+++ b/tap.c
@@ -490,10 +490,6 @@ static size_t tap_send_frames_vhost(const struct ctx *c,
vhost_kick(&vring_used_all[VHOST_USED_Q_IDX],
c->vhost.vq[VHOST_USED_Q_IDX].kick_fd);
- /* wait until the kernel finishes processing this send */
- while (avail->idx != vring_used_all[1].used.idx)
- ;
-
return processed_frames;
}
@@ -521,7 +517,7 @@ static size_t tap_send_frames_pasta(const struct ctx *c,
size_t i;
if (vhost)
- return tap_send_frames_vhost(c, iov, bufs_per_frame, nframes);
+ return tap_send_frames_vhost(c, iov, bufs_per_frame, nframes);
for (i = 0; i < nbufs; i += bufs_per_frame) {
ssize_t rc = writev(c->fd_tap, iov + i, bufs_per_frame);
diff --git a/tcp_buf.c b/tcp_buf.c
index 493c5c9..425410f 100644
--- a/tcp_buf.c
+++ b/tcp_buf.c
@@ -59,7 +59,22 @@ static_assert(MSS6 <= sizeof(tcp_payload[0].data), "MSS6 is greater than 65516")
/* References tracking the owner connection of frames in the tap outqueue */
static struct tcp_tap_conn *tcp_frame_conns[TCP_FRAMES_MEM];
-static unsigned int tcp_payload_used;
+
+/**
+ * struct tcp_payload_idx - Grow-only cursors into the TCP frame buffers
+ * @tcp_buf_idx: The last index we wrote data to in the tcp buffers
+ * @processed: Last index we pushed to the underlying connection
+ */
+static struct tcp_payload_idx {
+ unsigned int tcp_buf_idx;
+ unsigned int processed;
+} tcp_idx;
+
+/* latest index we wrote to into the tcp buffers */
+#define TCP_CURR_IDX (tcp_idx.tcp_buf_idx % TCP_FRAMES_MEM)
+
+/* current amount of frames queued in tcp buffers but not sent */
+#define TCP_FRAME_COUNT (tcp_idx.tcp_buf_idx - tcp_idx.processed)
/* recvmsg()/sendmsg() data for tap */
static struct iovec iov_sock [TCP_FRAMES_MEM + DISCARD_IOV_NUM];
@@ -158,15 +173,46 @@ static void tcp_revert_seq(const struct ctx *c, struct tcp_tap_conn **conns,
*/
void tcp_payload_flush(const struct ctx *c, const struct timespec *now)
{
- size_t m;
+ unsigned int total = TCP_FRAME_COUNT;
+ unsigned int start = tcp_idx.processed % TCP_FRAMES_MEM;
+ unsigned int first_batch_size, sent;
- m = tap_send_frames(c, &tcp_l2_iov[0][0], TCP_NUM_IOVS,
- tcp_payload_used);
- if (m != tcp_payload_used) {
- tcp_revert_seq(c, &tcp_frame_conns[m], &tcp_l2_iov[m],
- tcp_payload_used - m, now);
+ if (!total)
+ return;
+
+ /* What is smaller ? all we what we want to send ? or 128 - the index
+ * indicating the start of where we wrote this batch ?
+ */
+ first_batch_size = MIN(total, TCP_FRAMES_MEM - start);
+
+ sent = tap_send_frames(c, &tcp_l2_iov[start][0], TCP_NUM_IOVS,
+ first_batch_size);
+ if (sent < first_batch_size) {
+ tcp_revert_seq(c, &tcp_frame_conns[start + sent],
+ &tcp_l2_iov[start + sent],
+ first_batch_size - sent, now);
+ goto out;
}
- tcp_payload_used = 0;
+
+ /* There was more data to send than from tcp_idx.processed up to 128.
+ * the rest of the batch is going to be at index 0 up total -
+ * first_batch_size.
+ */
+ if (total > first_batch_size) {
+ unsigned int second_batch_size = total - first_batch_size;
+ size_t m2;
+
+ m2 = tap_send_frames(c, &tcp_l2_iov[0][0], TCP_NUM_IOVS,
+ second_batch_size);
+ sent += m2;
+
+ if (m2 < second_batch_size)
+ tcp_revert_seq(c, &tcp_frame_conns[m2], &tcp_l2_iov[m2],
+ second_batch_size - m2, now);
+ }
+
+out:
+ tcp_idx.processed += sent;
}
/**
@@ -244,13 +290,13 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
uint32_t seq;
int ret;
- iov = tcp_l2_iov[tcp_payload_used];
+ iov = tcp_l2_iov[TCP_CURR_IDX];
if (CONN_V4(conn))
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[TCP_CURR_IDX]);
else
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[TCP_CURR_IDX]);
- iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp_eth_hdr[tcp_payload_used]);
+ iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp_eth_hdr[TCP_CURR_IDX]);
payload = iov[TCP_IOV_PAYLOAD].iov_base;
seq = conn->seq_to_tap;
ret = tcp_prepare_flags(c, conn, flags, &payload->th,
@@ -259,7 +305,8 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
if (ret <= 0)
return ret;
- tcp_frame_conns[tcp_payload_used++] = conn;
+ tcp_idx.tcp_buf_idx++;
+ tcp_frame_conns[TCP_CURR_IDX] = conn;
l4len = optlen + sizeof(struct tcphdr);
iov[TCP_IOV_PAYLOAD].iov_len = l4len;
@@ -271,8 +318,9 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
tcp_l2_buf_pad(iov);
if (flags & DUP_ACK) {
- struct iovec *dup_iov = tcp_l2_iov[tcp_payload_used];
- tcp_frame_conns[tcp_payload_used++] = conn;
+ struct iovec *dup_iov = tcp_l2_iov[TCP_CURR_IDX];
+ tcp_frame_conns[TCP_CURR_IDX] = conn;
+ tcp_idx.tcp_buf_idx++;
memcpy(dup_iov[TCP_IOV_TAP].iov_base, iov[TCP_IOV_TAP].iov_base,
iov[TCP_IOV_TAP].iov_len);
@@ -284,7 +332,7 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
dup_iov[TCP_IOV_ETH_PAD].iov_len = iov[TCP_IOV_ETH_PAD].iov_len;
}
- if (tcp_payload_used > TCP_FRAMES_MEM - 2)
+ if (TCP_FRAME_COUNT > TCP_FRAMES_MEM - 2)
tcp_payload_flush(c, now);
return 0;
@@ -309,21 +357,29 @@ static void tcp_data_to_tap(const struct ctx *c, struct tcp_tap_conn *conn,
struct iovec *iov;
conn->seq_to_tap = seq + dlen;
- tcp_frame_conns[tcp_payload_used] = conn;
- iov = tcp_l2_iov[tcp_payload_used];
+ tcp_frame_conns[TCP_CURR_IDX] = conn;
+ iov = tcp_l2_iov[TCP_CURR_IDX];
if (CONN_V4(conn)) {
if (no_csum) {
- struct iovec *iov_prev = tcp_l2_iov[tcp_payload_used - 1];
+ /* TCP_CURR_IDX may be zero if the underlying
+ * tcp_idx.tcp_buf_idx is a multiple of 128, minus one
+ * will yield an invalid index. The previous index to 0
+ * is 127.
+ */
+ unsigned int prev_idx = (TCP_CURR_IDX +
+ TCP_FRAMES_MEM - 1) %
+ TCP_FRAMES_MEM;
+ struct iovec *iov_prev = tcp_l2_iov[prev_idx];
const struct iphdr *iph = iov_prev[TCP_IOV_IP].iov_base;
/* overwrite IP4_CSUM flag as we set the checksum */
check = iph->check;
}
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[TCP_CURR_IDX]);
} else if (CONN_V6(conn)) {
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[TCP_CURR_IDX]);
}
- iov[TCP_IOV_ETH].iov_base = &tcp_eth_hdr[tcp_payload_used];
+ iov[TCP_IOV_ETH].iov_base = &tcp_eth_hdr[TCP_CURR_IDX];
payload = iov[TCP_IOV_PAYLOAD].iov_base;
payload->th.th_off = sizeof(struct tcphdr) / 4;
payload->th.th_x2 = 0;
@@ -335,7 +391,8 @@ static void tcp_data_to_tap(const struct ctx *c, struct tcp_tap_conn *conn,
tcp_l2_buf_pad(iov);
- if (++tcp_payload_used > TCP_FRAMES_MEM - 1)
+ tcp_idx.tcp_buf_idx++;
+ if (TCP_FRAME_COUNT > TCP_FRAMES_MEM - 1)
tcp_payload_flush(c, now);
}
@@ -375,15 +432,13 @@ int tcp_buf_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn,
return -1;
}
- if (tcp_payload_used + fill_bufs > TCP_FRAMES_MEM) {
+ if (TCP_FRAME_COUNT + (unsigned int)fill_bufs > TCP_FRAMES_MEM)
tcp_payload_flush(c, now);
- /* Silence Coverity CWE-125 false positive */
- tcp_payload_used = 0;
- }
-
for (i = 0, iov = iov_sock + DISCARD_IOV_NUM; i < fill_bufs; i++, iov++) {
- iov->iov_base = &tcp_payload[tcp_payload_used + i].data;
+ unsigned int idx = (TCP_CURR_IDX + i) % TCP_FRAMES_MEM;
+
+ iov->iov_base = &tcp_payload[idx].data;
iov->iov_len = mss;
}
if (iov_rem)
@@ -457,7 +512,7 @@ int tcp_buf_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn,
dlen = mss;
seq = conn->seq_to_tap;
for (i = 0; i < send_bufs; i++) {
- int no_csum = i && i != send_bufs - 1 && tcp_payload_used;
+ int no_csum = i && i != send_bufs - 1;
bool push = false;
if (i == send_bufs - 1) {
--
2.39.5 (Apple Git-154)
1
0