[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Debian jessie > buster IPv6 link scope change of behaviour



On Thu, Jan 21, 2021 at 06:23:56PM -0600, David Wright wrote:
> Yes, that documents what we normally observe as a %eth0 or %1 suffix
> for IPv6 addresses which selects the interface to use. "Requires"
> (unemphasised in the original) mean that it is necessary to identify a
> particular zone, but IMHO doesn't mean that a choice of zone is

My opinion is that link-local addresses are ambiguous and it is required
to find a mean to deambiguate those, either using a zone (interface
identifier) %postfix, or through a default zone).

Actually, my interpretation of what happened is wrong.  If `ping'
exhibits a bizarre behaviour using apparently the first Ethernet
interface it sees, this is is not what other commands shows:

   $ telnet fe80::1
   Trying fe80::1...
   telnet: Unable to connect to remote host: Invalid argument

That's good (to me).

   schaefer@reliand:~$ telnet -6 fe80::1%eth1
   Trying fe80::1%eth1...
   [no error, NDP requests sent]

That's correct behaviour (to me).

ping however seems to do things itself and chooses an interface:

   PING fe80::1(fe80::1) 56 data bytes
   From fe80::9e8e:99ff:fe3c:5523%eth0: icmp_seq=1 Destination unreachable: Address unreachable

(which in this case, obviously, is not the correct one, but how
 could he guess? link-local v6 addresses ARE ambiguous -- should it
 try all other interfaces each like Microsoft apparently does as you
 mentionned?  no -- and it doesn't!)

   Also, a small code I wrote using getaddrinfo(3) and connect(2):
   $ ./simple-client fe80::1 80
   Trying connection to host fe80::1:80 ...
   connect(): : Invalid argument
   Could not connect.

   vs

   $ ./simple-client fe80::1%eth1 80
   Trying connection to host fe80::1%eth1:80 ...

So its's not getaddrinfo(3) which does bad things either.

So it's must be ping's fault.

So, let's look at ping's manpage:

       -I interface
           interface is either an address, or an interface name. If interface
           is an address, it sets source address to specified interface
           address. If interface in an interface name, it sets source
           interface to specified interface. NOTE: For IPv6, when doing ping
           to a link-local scope address, link specification (by the
           '%'-notation in destination, or by this option) can be used but it
           is no longer required.

Oho, "is no longer required" ... why that?

Let's look at the source:

apt-get source iputils-ping # especially ping6-common.c

In ping6_run() there is all sort of code related to LINKLOCAL v6
addresses and using the `device' global, which is initialized
in ping.c if -I is used, and is NULL in my use case.

It seems firsthop.sin6_scope_id is set manually when required.

Reading:
https://tools.ietf.org/id/draft-smith-ipv6-link-locals-apps-00.html#rfc.section.5
(about sin6_scope_id) and
https://tools.ietf.org/id/draft-smith-ipv6-link-locals-apps-00.html#rfc.section.5
(about getaddrinfo)

which confirms that link-local addresses are returned by getaddrinfo()
without scope id, unless they have the scope as the %postfix. So you
need to supply it separately (-I ping option would do). However, ping
seems to get overly smart about this, opening a probe socket on
apparently the first interface it gets, and getting the interface from
it, it seems.

Demonstration:

$ ./a.out fe80::1
Trying connection to host fe80::1:80 ...
scope ID: 0

./a.out fe80::1%eth0
Trying connection to host fe80::1%eth1:80 ...
scope ID: 2

Code:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>

int main(int argc, char **argv) {
   if (argc < 2) {
      fprintf(stderr, "%s v6-addr\n, %s: bad args.\n", argv[0], argv[0]);
      return 2;
   }

   struct addrinfo hints;
   struct addrinfo *result, *rp;

   hints.ai_family = AF_UNSPEC; /* IPv4 or v6 */
   hints.ai_socktype = SOCK_STREAM; /* TCP */
   hints.ai_flags = 0;
   hints.ai_protocol = 0; /* any protocol */
   int s;

   if ((s = getaddrinfo(argv[1], "http", &hints, &result))) {
      fprintf(stderr, "getaddrinfo(): failed: %s.\n", gai_strerror(s));
   }
   else {
      /*  getaddrinfo() returns a list of address structures.
       *  Try each address until we successfully connect(2).
       */
      for (rp = result; rp != NULL; rp = rp->ai_next) {
         char ipname[INET6_ADDRSTRLEN]; /* len(addrv6) > len(addrv4) */
         char servicename[6]; /* "65535\0" */
         if (!getnameinfo(rp->ai_addr,
                          rp->ai_addrlen,
                          ipname,
                          sizeof(ipname),
                          servicename,
                          sizeof(servicename),
                          NI_NUMERICHOST|NI_NUMERICSERV)) {
            printf("Trying connection to host %s:%s ...\n",
                   ipname,
                   servicename);
            if (rp->ai_family == AF_INET6) { 
               printf("scope ID: %d\n", ((struct sockaddr_in6 *) rp->ai_addr)->sin6_scope_id);
            }
         }
      }
   }

   return 0;
}


Reply to: