From: Paul Holzinger <pholzing@redhat.com>
To: Stefano Brivio <sbrivio@redhat.com>
Cc: David Gibson <david@gibson.dropbear.id.au>, passt-dev@passt.top
Subject: Re: [PATCH] netlink: Fix selection of template interface
Date: Wed, 20 Mar 2024 12:34:54 +0100 [thread overview]
Message-ID: <f2a8ee13-67e7-4e83-93ec-434e30321df6@redhat.com> (raw)
In-Reply-To: <20240320120243.49516b90@elisabeth>
On 20/03/2024 12:02, Stefano Brivio wrote:
> On Wed, 20 Mar 2024 11:51:59 +0100
> Paul Holzinger <pholzing@redhat.com> wrote:
>
>> On 20/03/2024 06:33, David Gibson wrote:
>>> Since f919dc7a4b1c ("conf, netlink: Don't require a default route to
>>> start"), if there is only one host interface with routes, we will pick that
>>> as the template interface, even if there are no default routes for an IP
>>> version. Unfortunately this selection had a serious flaw: in some cases
>>> it would 'return' in the middle of an nl_foreach() loop, meaning we
>>> wouldn't consume all the netlink responses for our query. This could cause
>>> later netlink operations to fail as we read leftover responses from the
>>> aborted query.
>>>
>>> Rewrite the interface detection to avoid this problem. While we're there:
>>> * Perform detection of both default and non-default routes in a single
>>> pass, avoiding an ugly goto
>>> * Give more detail on error and working but unusual paths about the
>>> situation (no suitable interface, multiple possible candidates, etc.).
>>>
>>> Fixes: f919dc7a4b1c ("conf, netlink: Don't require a default route to start")
>>> Link: https://bugs.passt.top/show_bug.cgi?id=83
>>> Link: https://github.com/containers/podman/issues/22052
>>> Link: https://bugzilla.redhat.com/show_bug.cgi?id=2270257
>>>
>>> Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
>>> ---
>>> conf.c | 4 ++--
>>> netlink.c | 62 ++++++++++++++++++++++++++++++++++---------------------
>>> 2 files changed, 40 insertions(+), 26 deletions(-)
>>>
>>> diff --git a/conf.c b/conf.c
>>> index 644752cc..9e0318a5 100644
>>> --- a/conf.c
>>> +++ b/conf.c
>>> @@ -584,7 +584,7 @@ static unsigned int conf_ip4(unsigned int ifi,
>>> ifi = nl_get_ext_if(nl_sock, AF_INET);
>>>
>>> if (!ifi) {
>>> - info("No interface with a route for IPv4: disabling IPv4");
>>> + info("Couldn't pick external interface: disabling IPv4");
>>> return 0;
>>> }
>>>
>>> @@ -656,7 +656,7 @@ static unsigned int conf_ip6(unsigned int ifi,
>>> ifi = nl_get_ext_if(nl_sock, AF_INET6);
>>>
>>> if (!ifi) {
>>> - info("No interface with a route for IPv6: disabling IPv6");
>>> + info("Couldn't pick external interface: disabling IPv6");
>>> return 0;
>>> }
>>>
>>> diff --git a/netlink.c b/netlink.c
>>> index 632304c1..c0a5f158 100644
>>> --- a/netlink.c
>>> +++ b/netlink.c
>>> @@ -254,8 +254,8 @@ unsigned int nl_get_ext_if(int s, sa_family_t af)
>>> .rtm.rtm_type = RTN_UNICAST,
>>> .rtm.rtm_family = af,
>>> };
>>> - bool default_only = true;
>>> - unsigned int ifi = 0;
>>> + unsigned defifi = 0, anyifi = 0;
>>> + unsigned ndef = 0, nany = 0;
>>> struct nlmsghdr *nh;
>>> struct rtattr *rta;
>>> char buf[NLBUFSIZ];
>>> @@ -263,7 +263,6 @@ unsigned int nl_get_ext_if(int s, sa_family_t af)
>>> uint32_t seq;
>>> size_t na;
>>>
>>> -again:
>>> /* Look for an interface with a default route first, failing that, look
>>> * for any interface with a route, and pick it only if it's the only
>>> * interface with a route.
>>> @@ -271,46 +270,61 @@ again:
>>> seq = nl_send(s, &req, RTM_GETROUTE, NLM_F_DUMP, sizeof(req));
>>> nl_foreach_oftype(nh, status, s, buf, seq, RTM_NEWROUTE) {
>>> struct rtmsg *rtm = (struct rtmsg *)NLMSG_DATA(nh);
>>> + unsigned thisifi = 0;
>>>
>>> - if (default_only) {
>>> - if (ifi || rtm->rtm_dst_len || rtm->rtm_family != af)
>>> - continue;
>>> - } else {
>>> - if (rtm->rtm_family != af)
>>> - continue;
>>> - }
>>> + if (rtm->rtm_family != af)
>>> + continue;
>>>
>>> for (rta = RTM_RTA(rtm), na = RTM_PAYLOAD(nh); RTA_OK(rta, na);
>>> rta = RTA_NEXT(rta, na)) {
>>> if (rta->rta_type == RTA_OIF) {
>>> - if (!default_only && ifi &&
>>> - ifi != *(unsigned int *)RTA_DATA(rta))
>>> - return 0;
>>> -
>>> - ifi = *(unsigned int *)RTA_DATA(rta);
>>> + thisifi = *(unsigned int *)RTA_DATA(rta);
>>> } else if (rta->rta_type == RTA_MULTIPATH) {
>>> const struct rtnexthop *rtnh;
>>>
>>> rtnh = (struct rtnexthop *)RTA_DATA(rta);
>>> + thisifi = rtnh->rtnh_ifindex;
>>> + }
>>> + }
>>>
>>> - if (!default_only && ifi &&
>>> - (int)ifi != rtnh->rtnh_ifindex)
>>> - return 0;
>>> + if (!thisifi)
>>> + continue; /* No interface for this route */
>>>
>>> - ifi = rtnh->rtnh_ifindex;
>>> - }
>>> + if (rtm->rtm_dst_len == 0) {
>>> + /* Default route */
>>> + ndef++;
>>> + if (!defifi)
>>> + defifi = thisifi;
>>> + } else {
>>> + /* Non-default route */
>>> + nany++;
>>> + if (!anyifi)
>>> + anyifi = thisifi;
>>> }
>>> }
>>>
>>> if (status < 0)
>>> warn("netlink: RTM_GETROUTE failed: %s", strerror(-status));
>>>
>>> - if (!ifi && default_only) {
>>> - default_only = false;
>>> - goto again;
>>> + if (defifi) {
>>> + if (ndef > 1)
>>> + info("Multiple default %s routes, picked first",
>>> + af == AF_INET ? "IPv4" : "IPv6");
>>> + return defifi;
>>> }
>>>
>>> - return ifi;
>>> + if (anyifi) {
>>> + if (nany == 1)
>>> + return anyifi;
>>> +
>>> + warn("Multiple interfaces with %s routes, use -i to select one",
>>> + af == AF_INET ? "IPv4" : "IPv6");
>> This should not be a warning, for me this always triggers because I have
>> two interfaces with link local addresses and no global ipv6 route as I
>> do not have any ipv6 connection.
> I was about to reply as I just applied this with s/warn/info/ here :)
The more I think about this I think this suggestion is just wrong. I
have a valid ipv4 connection and no ipv6, offering me to use -i is just
wrong as it will not help me, especially if one uses a different
interface for ipv4/ipv6 connections.
>
>> Or maybe the correct fix is to never consider ipv6 link local routes for
>> this logic? At least I cannot see the purpose of using a interface with
>> only a link local route.
>>
>>> + }
>>> +
>>> + if (!nany)
>>> + warn("No interfaces with %s routes", af == AF_INET ? "IPv4" : "IPv6");
> ...and here, because if one has no IPv6 routes we would reintroduce the
> issue we just fixed in 338b6321ac0d ("conf: No routable interface for
> IPv4 or IPv6 is informational, not a warning").
>
> I think the purpose of picking interfaces based on routes for
> link-local destinations is for practical test setups like the one
> described in https://github.com/containers/podman/issues/21896.
>
> Functionally it doesn't make sense, but it shouldn't harm either
> (right?).
I am not sure how the issue is related to link local addresses, for ipv6
a link local route must explicitly never be routed anywhere else besides
the current interface so picking this seems incorrect as it will be
unusable not matter what.
>
>>> +
>>> + return 0;
>>> }
>>>
>>> /**
>> I reproduced by having a second interface and confirm this patch fixes it.
> Thanks for checking! Let's hope that was the case in the failing test.
>
next prev parent reply other threads:[~2024-03-20 11:34 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-03-20 5:33 [PATCH] netlink: Fix selection of template interface David Gibson
2024-03-20 10:51 ` Paul Holzinger
2024-03-20 11:02 ` Stefano Brivio
2024-03-20 11:34 ` Paul Holzinger [this message]
2024-03-20 12:49 ` 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=f2a8ee13-67e7-4e83-93ec-434e30321df6@redhat.com \
--to=pholzing@redhat.com \
--cc=david@gibson.dropbear.id.au \
--cc=passt-dev@passt.top \
--cc=sbrivio@redhat.com \
/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).