From: Stefano Brivio <sbrivio@redhat.com>
To: Paul Holzinger <pholzing@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:02:43 +0100 [thread overview]
Message-ID: <20240320120243.49516b90@elisabeth> (raw)
In-Reply-To: <80107179-5357-4b14-94ae-c82925a97ba8@redhat.com>
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 :)
> 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?).
> > +
> > + 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.
--
Stefano
next prev parent reply other threads:[~2024-03-20 11:03 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 [this message]
2024-03-20 11:34 ` Paul Holzinger
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=20240320120243.49516b90@elisabeth \
--to=sbrivio@redhat.com \
--cc=david@gibson.dropbear.id.au \
--cc=passt-dev@passt.top \
--cc=pholzing@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).