public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: David Gibson <david@gibson.dropbear.id.au>
To: passt-dev@passt.top
Subject: [PATCH 2/7] Separately locate external interfaces for IPv4 and IPv6
Date: Fri, 22 Jul 2022 15:31:13 +1000	[thread overview]
Message-ID: <20220722053118.1067459-3-david@gibson.dropbear.id.au> (raw)
In-Reply-To: <20220722053118.1067459-1-david@gibson.dropbear.id.au>

[-- Attachment #1: Type: text/plain, Size: 9947 bytes --]

Now that the back end allows passt/pasta to use different external
interfaces for IPv4 and IPv6, use that to do the right thing in the case
that the host has IPv4 and IPv6 connectivity via different interfaces.
If the user hasn't explicitly chosen an interface, separately search for
a suitable external interface for each protocol.

As a bonus, this substantially simplifies the external interface probe.  It
also eliminates a subtle confusing case where in some circumstances we
would pick the first interface in interface index order, and sometimes in
order of routes returned from netlink.  On some network configurations that
could cause tests to fail, because the logic in the tests was subtly
different (it always used route order).

Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au>
---
 conf.c                | 19 +++++++++--
 netlink.c             | 79 ++++---------------------------------------
 netlink.h             |  2 +-
 test/dhcp/passt       |  3 +-
 test/dhcp/pasta       |  3 +-
 test/ndp/passt        |  4 +--
 test/two_guests/basic |  3 +-
 7 files changed, 33 insertions(+), 80 deletions(-)

diff --git a/conf.c b/conf.c
index 943526d..cc08de3 100644
--- a/conf.c
+++ b/conf.c
@@ -630,8 +630,23 @@ static void conf_ip(struct ctx *c)
 		v4 = v6		= IP_VERSION_PROBE;
 	}
 
-	if (!c->ifi4 && !c->ifi6)
-		c->ifi4 = c->ifi6 = nl_get_ext_if(&v4, &v6);
+	if (v4 != IP_VERSION_DISABLED) {
+		if (!c->ifi4)
+			c->ifi4 = nl_get_ext_if(AF_INET);
+		if (!c->ifi4) {
+			warn("No external routable interface for IPv4");
+			v4 = IP_VERSION_DISABLED;
+		}
+	}
+
+	if (v6 != IP_VERSION_DISABLED) {
+		if (!c->ifi6)
+			c->ifi6 = nl_get_ext_if(AF_INET6);
+		if (!c->ifi6) {
+			warn("No external routable interface for IPv6");
+			v6 = IP_VERSION_DISABLED;
+		}
+	}
 
 	if (v4 != IP_VERSION_DISABLED) {
 		if (!c->gw4)
diff --git a/netlink.c b/netlink.c
index 66a95e4..8ad6a0c 100644
--- a/netlink.c
+++ b/netlink.c
@@ -126,13 +126,13 @@ static int nl_req(int ns, char *buf, const void *req, ssize_t len)
 }
 
 /**
- * nl_get_ext_if() - Get interface index supporting IP versions being probed
- * @v4:		Probe IPv4 support, set to ENABLED or DISABLED on return
- * @v6:		Probe IPv4 support, set to ENABLED or DISABLED on return
+ * nl_get_ext_if() - Get interface index supporting IP version being probed
+ * @af:	Address family (AF_INET or AF_INET6) to look for connectivity
+ *      for.
  *
  * Return: interface index, 0 if not found
  */
-unsigned int nl_get_ext_if(int *v4, int *v6)
+unsigned int nl_get_ext_if(sa_family_t af)
 {
 	struct { struct nlmsghdr nlh; struct rtmsg rtm; } req = {
 		.nlh.nlmsg_type	 = RTM_GETROUTE,
@@ -143,32 +143,14 @@ unsigned int nl_get_ext_if(int *v4, int *v6)
 		.rtm.rtm_table	 = RT_TABLE_MAIN,
 		.rtm.rtm_scope	 = RT_SCOPE_UNIVERSE,
 		.rtm.rtm_type	 = RTN_UNICAST,
+		.rtm.rtm_family	 = af,
 	};
-	unsigned int i, first_v4 = 0, first_v6 = 0;
-	uint8_t has_v4[PAGE_SIZE * 8 / 8] = { 0 }; /* See __dev_alloc_name() */
-	uint8_t has_v6[PAGE_SIZE * 8 / 8] = { 0 }; /* in kernel */
 	struct nlmsghdr *nh;
 	struct rtattr *rta;
 	struct rtmsg *rtm;
 	char buf[BUFSIZ];
-	long *word, tmp;
-	uint8_t *vmap;
 	ssize_t n;
 	size_t na;
-	int *v;
-
-	if (*v4 == IP_VERSION_PROBE) {
-		v = v4;
-		req.rtm.rtm_family = AF_INET;
-		vmap = has_v4;
-	} else if (*v6 == IP_VERSION_PROBE) {
-v6:
-		v = v6;
-		req.rtm.rtm_family = AF_INET6;
-		vmap = has_v6;
-	} else {
-		return 0;
-	}
 
 	if ((n = nl_req(0, buf, &req, sizeof(req))) < 0)
 		return 0;
@@ -178,7 +160,7 @@ v6:
 	for ( ; NLMSG_OK(nh, n); nh = NLMSG_NEXT(nh, n)) {
 		rtm = (struct rtmsg *)NLMSG_DATA(nh);
 
-		if (rtm->rtm_dst_len || rtm->rtm_family != req.rtm.rtm_family)
+		if (rtm->rtm_dst_len || rtm->rtm_family != af)
 			continue;
 
 		for (rta = RTM_RTA(rtm), na = RTM_PAYLOAD(nh); RTA_OK(rta, na);
@@ -190,57 +172,10 @@ v6:
 
 			ifi = *(unsigned int *)RTA_DATA(rta);
 
-			if (*v4 == IP_VERSION_DISABLED ||
-			    *v6 == IP_VERSION_DISABLED) {
-				*v = IP_VERSION_ENABLED;
-				return ifi;
-			}
-
-			if (v == v4 && !first_v4)
-				first_v4 = ifi;
-
-			if (v == v6 && !first_v6)
-				first_v6 = ifi;
-
-			bitmap_set(vmap, ifi);
+			return ifi;
 		}
 	}
 
-	if (v == v4 && *v6 == IP_VERSION_PROBE) {
-		req.nlh.nlmsg_seq = nl_seq++;
-		goto v6;
-	}
-
-	word = (long *)has_v4;
-	for (i = 0; i < ARRAY_SIZE(has_v4) / sizeof(long); i++, word++) {
-		tmp = *word;
-		while ((n = ffsl(tmp))) {
-			int ifi = i * sizeof(long) * 8 + n - 1;
-
-			if (!first_v4)
-				first_v4 = ifi;
-
-			tmp &= ~(1UL << (n - 1));
-			if (bitmap_isset(has_v6, ifi)) {
-				*v4 = *v6 = IP_VERSION_ENABLED;
-				return ifi;
-			}
-		}
-	}
-
-	if (first_v4) {
-		*v4 = IP_VERSION_ENABLED;
-		*v6 = IP_VERSION_DISABLED;
-		return first_v4;
-	}
-
-	if (first_v6) {
-		*v4 = IP_VERSION_DISABLED;
-		*v6 = IP_VERSION_ENABLED;
-		return first_v6;
-	}
-
-	err("No external routable interface for any IP protocol");
 	return 0;
 }
 
diff --git a/netlink.h b/netlink.h
index 261904e..5ce5037 100644
--- a/netlink.h
+++ b/netlink.h
@@ -7,7 +7,7 @@
 #define NETLINK_H
 
 int nl_sock_init(const struct ctx *c);
-unsigned int nl_get_ext_if(int *v4, int *v6);
+unsigned int nl_get_ext_if(sa_family_t af);
 void nl_route(int ns, unsigned int ifi, sa_family_t af, void *gw);
 void nl_addr(int ns, unsigned int ifi, sa_family_t af,
 	     void *addr, int *prefix_len, void *addr_l);
diff --git a/test/dhcp/passt b/test/dhcp/passt
index f45227a..11e0eb3 100644
--- a/test/dhcp/passt
+++ b/test/dhcp/passt
@@ -17,6 +17,7 @@ htools	ip jq sed tr head
 test	Interface name
 gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
 hout	HOST_IFNAME ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
 check	[ -n "__IFNAME__" ]
 
 test	DHCP: address
@@ -49,7 +50,7 @@ check	[ "__SEARCH__" = "__HOST_SEARCH__" ]
 test	DHCPv6: address
 guest	/sbin/dhclient -6 __IFNAME__
 gout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.prefixlen == 128).local'
-hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME__").addr_info[] | select(.scope == "global").local'
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global").local'
 check	[ "__ADDR6__" = "__HOST_ADDR6__" ]
 
 test	DHCPv6: route
diff --git a/test/dhcp/pasta b/test/dhcp/pasta
index 65b1d42..076ec8d 100644
--- a/test/dhcp/pasta
+++ b/test/dhcp/pasta
@@ -35,8 +35,9 @@ check	[ __MTU__ = 65520 ]
 
 test	DHCPv6: address
 ns	/sbin/dhclient -6 --no-pid __IFNAME__
+hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
 nsout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.prefixlen == 128).local'
-hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global").local'
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global").local'
 check	[ __ADDR6__ = __HOST_ADDR6__ ]
 
 test	DHCPv6: route
diff --git a/test/ndp/passt b/test/ndp/passt
index d6b4c40..8ef15e7 100644
--- a/test/ndp/passt
+++ b/test/ndp/passt
@@ -17,13 +17,13 @@ htools	ip jq sipcalc grep cut
 test	Interface name
 gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
 guest	ip link set dev __IFNAME__ up && sleep 2
-hout	HOST_IFNAME ip -j -4 route show|jq -rM '.[] | select(.dst == "default").dev'
+hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").dev'
 check	[ -n "__IFNAME__" ]
 
 test	SLAAC: prefix
 gout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global" and .prefixlen == 64).local'
 gout	PREFIX6 sipcalc __ADDR6__/64 | grep prefix | cut -d' ' -f4
-hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME__").addr_info[] | select(.scope == "global").local'
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global").local'
 hout	HOST_PREFIX6 sipcalc __HOST_ADDR6__/64 | grep prefix | cut -d' ' -f4
 check	[ "__PREFIX6__" = "__HOST_PREFIX6__" ]
 
diff --git a/test/two_guests/basic b/test/two_guests/basic
index f7c016d..e226178 100644
--- a/test/two_guests/basic
+++ b/test/two_guests/basic
@@ -19,6 +19,7 @@ test	Interface names
 g1out	IFNAME1 ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
 g2out	IFNAME2 ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
 hout	HOST_IFNAME ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
 check	[ -n "__IFNAME1__" ]
 check	[ -n "__IFNAME2__" ]
 
@@ -40,7 +41,7 @@ guest1	/sbin/dhclient -6 __IFNAME1__
 guest2	/sbin/dhclient -6 __IFNAME2__
 g1out	ADDR1_6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME1__").addr_info[] | select(.prefixlen == 128).local'
 g2out	ADDR2_6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME2__").addr_info[] | select(.prefixlen == 128).local'
-hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME__").addr_info[] | select(.scope == "global").local'
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global").local'
 check	[ "__ADDR1_6__" = "__HOST_ADDR6__" ]
 check	[ "__ADDR2_6__" = "__HOST_ADDR6__" ]
 
-- 
@@ -19,6 +19,7 @@ test	Interface names
 g1out	IFNAME1 ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
 g2out	IFNAME2 ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
 hout	HOST_IFNAME ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
 check	[ -n "__IFNAME1__" ]
 check	[ -n "__IFNAME2__" ]
 
@@ -40,7 +41,7 @@ guest1	/sbin/dhclient -6 __IFNAME1__
 guest2	/sbin/dhclient -6 __IFNAME2__
 g1out	ADDR1_6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME1__").addr_info[] | select(.prefixlen == 128).local'
 g2out	ADDR2_6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME2__").addr_info[] | select(.prefixlen == 128).local'
-hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME__").addr_info[] | select(.scope == "global").local'
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global").local'
 check	[ "__ADDR1_6__" = "__HOST_ADDR6__" ]
 check	[ "__ADDR2_6__" = "__HOST_ADDR6__" ]
 
-- 
2.37.1


  parent reply	other threads:[~2022-07-22  5:31 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-07-22  5:31 [PATCH 0/7] Improved selection of external interface and IP configuration David Gibson
2022-07-22  5:31 ` [PATCH 1/7] Allow different external interfaces for IPv4 and IPv6 connectivity David Gibson
2022-07-22  5:31 ` David Gibson [this message]
2022-08-01 10:23   ` [PATCH 2/7] Separately locate external interfaces for IPv4 and IPv6 Stefano Brivio
2022-07-22  5:31 ` [PATCH 3/7] Initialize host side MAC when in IPv6 only mode David Gibson
2022-07-22  5:31 ` [PATCH 4/7] Move passt mac_guest init to be more symmetric with pasta David Gibson
2022-07-22  5:31 ` [PATCH 5/7] Clarify semantics of c->v4 and c->v6 variables David Gibson
2022-08-01 10:24   ` Stefano Brivio
2022-07-22  5:31 ` [PATCH 6/7] Separate IPv4 and IPv6 configuration David Gibson
2022-08-01 10:24   ` Stefano Brivio
2022-07-22  5:31 ` [PATCH 7/7] Make substructures for IPv4 and IPv6 specific context information David Gibson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220722053118.1067459-3-david@gibson.dropbear.id.au \
    --to=david@gibson.dropbear.id.au \
    --cc=passt-dev@passt.top \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).