router: skip RA and wait for LINK-LOCAL to be assigned
authorChristian Marangi <ansuelsmth@gmail.com>
Thu, 16 Mar 2023 23:56:25 +0000 (00:56 +0100)
committerChristian Marangi <ansuelsmth@gmail.com>
Wed, 22 Mar 2023 05:34:20 +0000 (06:34 +0100)
This fix a specific and corner case when the following error and similar
is printed in the log:

Failed to send to ff02::1%br-lan (Address not available)

The cause for this was tracked down to the lack of the interface of a
configured LINK-LOCAL IPV6 address resulting in odhcpd_send() always
failing.

A LINK-LOCAL IPV6 address is assigned only after the interface has
carrier and is set to IFF_RUNNING and require some time for the address
to be assigned due to DAD logic.

In the case where an interface was just UP, odhcpd RA may fail since the
LINK-LOCAL IPV6 address still needs to be assigned as it still need to
be "trained". From the kernel view this is flagged in the IPV6 interface
address with the flag IFA_F_TENTATIVE, that means the address still
needs to be checked and follow DAD process.

This is only a transient problem and the DAD process is required only
once till the interface is not set DOWN.

To handle this, add some check to verify if the address has to be
checked and add an additional bool to flag if the interface have a
LINK-LOCAL assigned.

Skip sending RA if the interface still doesn't have finished the DAD
process and retry at the next RA.
A notice log is added to track this special case to track problematic
case and even more corner case.

Logic to check if interface have LINK-LOCAL are:
- When interface is setup, on scanning for the interface ipv6 address
  check if at least one address is NOT in IFA_F_TENTATIVE state.
- With interface already up but with still no LINK-LOCAL react on the
  RTM_NEWADDR event and set LINK-LOCAL if the addrs added by the event
  is a LINK-LOCAL reflecting that the interface finally ended the DAD
  process and have a correct address.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Acked-by: Hans Dedecker <dedeckeh@gmail.com>
src/config.c
src/netlink.c
src/odhcpd.h
src/router.c

index 30da8794487e8f65577f14969df38d96d81a0b60..ee7219fa9a2724867f82f9ad83b638599bf4e5eb 100644 (file)
@@ -594,6 +594,15 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr
                if (len > 0)
                        iface->addr6_len = len;
 
+               for (size_t i = 0; i < iface->addr6_len; i++) {
+                       struct odhcpd_ipaddr *addr = &iface->addr6[i];
+
+                       if (!addr->tentative) {
+                               iface->have_link_local = true;
+                               break;
+                       }
+               }
+
                len = netlink_get_interface_addrs(iface->ifindex,
                                                false, &iface->addr4);
                if (len > 0)
index 4a352a65e4aaab16b5db14b6518998aa26594f6c..0a2da03bbc165c1fb34012e22923cfa98b908f0c 100644 (file)
@@ -386,7 +386,7 @@ static int handle_rtm_addr(struct nlmsghdr *hdr, bool add)
 
                nla_memcpy(&event_info.addr, nla[IFA_ADDRESS], sizeof(event_info.addr));
 
-               if (IN6_IS_ADDR_LINKLOCAL(&event_info.addr) || IN6_IS_ADDR_MULTICAST(&event_info.addr))
+               if (IN6_IS_ADDR_MULTICAST(&event_info.addr))
                        return NL_SKIP;
 
                inet_ntop(AF_INET6, &event_info.addr, buf, sizeof(buf));
@@ -395,6 +395,11 @@ static int handle_rtm_addr(struct nlmsghdr *hdr, bool add)
                        if (iface->ifindex != (int)ifa->ifa_index)
                                continue;
 
+                       if (add && IN6_IS_ADDR_LINKLOCAL(&event_info.addr)) {
+                               iface->have_link_local = true;
+                               return NL_SKIP;
+                       }
+
                        syslog(LOG_DEBUG, "Netlink %s %s on %s", add ? "newaddr" : "deladdr",
                                        buf, iface->name);
 
@@ -625,6 +630,10 @@ static int cb_addr_valid(struct nl_msg *msg, void *arg)
        if (ifa->ifa_flags & IFA_F_DEPRECATED)
                addrs[ctxt->ret].preferred = 0;
 
+       if (ifa->ifa_family == AF_INET6 &&
+           ifa->ifa_flags & IFA_F_TENTATIVE)
+               addrs[ctxt->ret].tentative = true;
+
        ctxt->ret++;
        *(ctxt->addrs) = addrs;
 
index d829033840552c1db5537733f1c520217d767ea3..0550bc28d4ebfcad87c46f41ac157cd9964d0ca5 100644 (file)
@@ -131,6 +131,7 @@ struct odhcpd_ipaddr {
                struct {
                        uint8_t dprefix;
                        uint8_t invalid_advertisements;
+                       bool tentative;
                };
 
                /* ipv4 only */
@@ -300,6 +301,7 @@ struct interface {
        bool ra_useleasetime;
        bool ra_dns;
        bool no_dynamic_dhcp;
+       bool have_link_local;
        uint8_t pio_filter_length;
        struct in6_addr pio_filter_addr;
        int default_router;
index 7e66e3c6418fbd5ca1b91dc7997c7dce03597892..eca0bf7a282278fed0489bfabf8448003ad3bef3 100644 (file)
@@ -621,6 +621,11 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
        msecs = calc_adv_interval(iface, minvalid, &maxival);
        lifetime = calc_ra_lifetime(iface, maxival);
 
+       if (!iface->have_link_local) {
+               syslog(LOG_NOTICE, "Skip sending a RA on %s as no link local address is available", iface->name);
+               goto out;
+       }
+
        if (default_route && valid_prefix) {
                adv.h.nd_ra_router_lifetime = htons(lifetime < UINT16_MAX ? lifetime : UINT16_MAX);
        } else {
@@ -782,6 +787,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
        if (odhcpd_send(iface->router_event.uloop.fd, &dest, iov, ARRAY_SIZE(iov), iface) > 0)
                iface->ra_sent++;
 
+out:
        free(pfxs);
        free(routes);