Add pfx_flush to flush ND6 entries when changing routes.
Both these functions only work for BSD, just stubs on Linux.
This tidies the code a little and fixes changing IPv6 routes on BSD,
although the kernel may complain network is down for a few moments.
memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
if (sendmsg(sock, &sndhdr, 0) == -1) {
- syslog(LOG_ERR, "%s: sendmsg: %m", ifp->name);
+ syslog(LOG_ERR, "%s: %s: sendmsg: %m", ifp->name, __func__);
ifp->options->options &= ~DHCPCD_IPV6;
dhcp6_drop(ifp, "EXPIRE6");
return -1;
if (carrier == -1)
syslog(LOG_ERR, "%s: carrier_status: %m", ifname);
- else if (carrier == 0 || ~ifp->flags & IFF_UP) {
+ else if (carrier == 0 ||
+ (ifp->flags & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING))
+ {
if (ifp->carrier != LINK_DOWN) {
ifp->carrier = LINK_DOWN;
syslog(LOG_INFO, "%s: carrier lost", ifp->name);
dhcp_close(ifp);
dhcp6_drop(ifp, "EXPIRE6");
ipv6rs_drop(ifp);
+ ipv6_free(ifp);
dhcp_drop(ifp, "NOCARRIER");
}
- } else if (carrier == 1 && !(~ifp->flags & IFF_UP)) {
+ } else if (carrier == 1 &&
+ (ifp->flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING))
+ {
if (ifp->carrier != LINK_UP) {
ifp->carrier = LINK_UP;
syslog(LOG_INFO, "%s: carrier acquired", ifp->name);
retval = -1;
return retval;
}
+
+int
+pfx_flush(void)
+{
+ int s;
+ char dummy[IFNAMSIZ + 1];
+
+ s = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (s != -1) {
+ strcpy(dummy, "lo0");
+ s = ioctl(s, SIOCSPFXFLUSH_IN6, (caddr_t)&dummy);
+ }
+ return s;
+}
#endif
static void
}
}
+int
+in6_addr_flags(const char *ifname, const struct in6_addr *addr)
+{
+ int s, flags;
+ struct in6_ifreq ifr6;
+
+ s = socket(AF_INET6, SOCK_DGRAM, 0);
+ flags = -1;
+ if (s != -1) {
+ memset(&ifr6, 0, sizeof(ifr6));
+ strncpy(ifr6.ifr_name, ifname, sizeof(ifr6.ifr_name));
+ ifr6.ifr_addr.sin6_family = AF_INET6;
+ ifr6.ifr_addr.sin6_addr = *addr;
+ if (ioctl(s, SIOCGIFAFLAG_IN6, &ifr6) != -1)
+ flags = ifr6.ifr_flags;
+ close(s);
+ }
+ return flags;
+}
+
int
manage_link(int fd)
{
#if defined(INET6) && !defined(LISTEN_DAD)
struct in6_addr ia6;
struct sockaddr_in6 *sin6;
+ int ifa_flags;
#endif
for (;;) {
memcpy(ia6.s6_addr,
sin6->sin6_addr.s6_addr,
sizeof(ia6.s6_addr));
+ if (rtm->rtm_type == RTM_NEWADDR) {
+ ifa_flags = in6_addr_flags(
+ ifname,
+ &ia6);
+ if (ifa_flags == -1)
+ break;
+ } else
+ ifa_flags = 0;
ipv6_handleifa(rtm->rtm_type, NULL,
- ifname, &ia6, ifam->ifam_flags);
+ ifname, &ia6, ifa_flags);
break;
#endif
}
errno = ENOTSUP;
return -1;
}
+
+int
+in6_addr_flags(const char *ifname, const struct in6_addr *addr)
+{
+
+ /* How do I get IPv6 address flags on Linux? */
+ return 0;
+}
+
+int
+pfx_flush(void)
+{
+
+ /* Flush prefixes, ip -6 neigh flush equivalent */
+ /* There seems to be no need for this on Linux right now though,
+ * probably due to route metrics */
+ return 0;
+}
#endif
#endif
static struct rt6head *routes;
+static uint8_t do_pfx_flush;
#ifdef DEBUG_MEMORY
static void
#endif
/* Safety - ignore tentative announcements */
- if (cmd == RTM_NEWADDR && flags & IN6_IFF_TENTATIVE)
- return;
+ if (cmd == RTM_NEWADDR) {
+ if (flags & (IN6_IFF_TENTATIVE || IN6_IFF_DUPLICATED))
+ cmd = RTM_DELADDR;
+#ifdef IN6_IFF_DETACHED
+ if (flags & IN6_IFF_DETACHED)
+ cmd = RTM_DELADDR;
+#endif
+ }
if (ifs == NULL)
ifs = ifaces;
const struct ipv6_state *state;
state = IPV6_CSTATE(ifp);
- return state && TAILQ_FIRST(&state->ll_addrs) ? 1 : 0;
+ return (state && TAILQ_FIRST(&state->ll_addrs)) ? 1 : 0;
}
int ipv6_addlinklocalcallback(struct interface *ifp,
free(cb);
}
free(state);
+ ifp->if_data[IF_DATA_IPV6] = NULL;
}
}
desc_route(add ? "adding" : "changing", nrt);
/* We delete and add the route so that we can change metric and
* prefer the interface. */
- del_route6(ort);
- if (!add_route6(nrt))
+ if (del_route6(ort) == 0 && add)
+ do_pfx_flush = 1;
+ if (add_route6(nrt) == 0)
return 0;
syslog(LOG_ERR, "%s: add_route6: %m", nrt->iface->name);
return -1;
struct ipv6_addr *addr;
const struct interface *ifp;
const struct dhcp6_state *d6_state;
- int have_default;
+ uint8_t have_default;
if (!(options & (DHCPCD_IPV6RA_OWN | DHCPCD_IPV6RA_OWN_DEFAULT)))
return;
return;
}
TAILQ_INIT(nrs);
- have_default = 0;
+ have_default = do_pfx_flush = 0;
TAILQ_FOREACH_SAFE(rt, &dnr, next, rtn) {
/* Is this route already in our table? */
if (find_route6(nrs, rt) != NULL)
{
if (c_route(or, rt) != 0)
continue;
+ do_pfx_flush = 1;
}
TAILQ_REMOVE(routes, or, next);
free(or);
free(rt);
}
+ if (do_pfx_flush) {
+ /* We need to flush all entries in our prefix list
+ * if we changed a route so that we leave via the
+ * correct interface */
+ if (pfx_flush() == -1)
+ syslog(LOG_ERR, "pfx_flush: %m");
+
+ /* XXX FIXME
+ * On multi-homed systems on the same network
+ * (ie wired and wireless) swapping the active routes
+ * between interfaces (ie, bringing down the wired and
+ * back up again while wireless is active) causes the
+ * ping6 command to emit bogus Network is down errors.
+ * This could be a kernel bug though */
+ }
+
free(routes);
routes = nrs;
}
void ipv6_free(struct interface *);
int ipv6_removesubnet(const struct interface *, struct ipv6_addr *);
void ipv6_buildroutes(void);
-void ipv6_drop(struct interface *);
#else
#define ipv6_init() -1
#define ipv6_free(a)
#endif
if (sendmsg(unspec_sock, &sndhdr, 0) == -1)
syslog(LOG_ERR, "%s: %s: sendmsg: %m",
- __func__, ap->iface->name);
+ ap->iface->name, __func__);
if (ap->dadcallback) {
ms_to_tv(&tv, RETRANS_TIMER);
#endif
if (sendmsg(sock, &sndhdr, 0) == -1)
syslog(LOG_ERR, "%s: %s: sendmsg: %m",
- __func__, rap->iface->name);
-
+ rap->iface->name, __func__);
ms_to_tv(&tv, rap->retrans == 0 ? RETRANS_TIMER : rap->retrans);
ms_to_tv(&rtv, MIN_RANDOM_FACTOR);
syslog(LOG_DEBUG, "%s: sending Router Solicitation", ifp->name);
if (sendmsg(sock, &sndhdr, 0) == -1) {
- syslog(LOG_ERR, "%s: sendmsg: %m", ifp->name);
+ syslog(LOG_ERR, "%s: %s: sendmsg: %m", ifp->name, __func__);
ipv6rs_drop(ifp);
ifp->options->options &= ~(DHCPCD_IPV6 | DHCPCD_IPV6RS);
return;
}
}
if (expired) {
- ipv6_buildroutes();
while ((rap = TAILQ_FIRST(&rtrs))) {
TAILQ_REMOVE(&rtrs, rap, next);
ipv6rs_drop_ra(rap);
}
+ ipv6_buildroutes();
script_runreason(ifp, "ROUTERADVERT");
}
}
#ifdef INET6
const struct sockaddr_in6 *sin6;
int ifa_flags;
-#ifdef SIOCGIFAFLAG_IN6
- struct in6_ifreq ifr6;
- int s6;
-#endif
#endif
#ifdef AF_LINK
const struct sockaddr_dl *sdl;
#ifdef INET6
/* Capture local link addresses */
-#ifdef SIOCGIFAFLAG_IN6
- s6 = socket(AF_INET6, SOCK_DGRAM, 0);
- if (s6 == -1)
- syslog(LOG_ERR, "%s: socket: %m", __func__);
-#endif
-
for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
if (ifa->ifa_addr != NULL &&
ifa->ifa_addr->sa_family == AF_INET6)
sin6 = (const struct sockaddr_in6 *)
(void *)ifa->ifa_addr;
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
-#ifdef SIOCGIFAFLAG_IN6
- memset(&ifr6, 0, sizeof(ifr6));
- strncpy(ifr6.ifr_name, ifa->ifa_name,
- sizeof(ifr6.ifr_name));
- ifr6.ifr_addr = *sin6;
- if (ioctl(s6, SIOCGIFAFLAG_IN6, &ifr6) == -1) {
- syslog(LOG_ERR,
- "%s: SIOCGIFAFLAG_IN6: %m",
- ifa->ifa_name);
- continue;
- }
- ifa_flags = ifr6.ifr_ifru.ifru_flags6;
-#else
- /* We have no way of getting the address flags
- * such as tentative on Linux at this point.
- * We have to hope that getifaddrs only returns
- * useable addresses. */
- ifa_flags = 0;
-#endif
- ipv6_handleifa(RTM_NEWADDR, ifs, ifa->ifa_name,
- &sin6->sin6_addr, ifa_flags);
+ ifa_flags = in6_addr_flags(ifa->ifa_name,
+ &sin6->sin6_addr);
+ if (ifa_flags != -1)
+ ipv6_handleifa(RTM_NEWADDR, ifs,
+ ifa->ifa_name,
+ &sin6->sin6_addr, ifa_flags);
}
}
}
-#ifdef SIOCGIFAFLAG_IN6
- if (s6 != -1)
- close(s6);
-#endif
#endif
freeifaddrs(ifaddrs);
int if_address6(const struct interface *, const struct ipv6_addr *, int);
#define add_address6(ifp, a) if_address6(ifp, a, 1)
#define del_address6(ifp, a) if_address6(ifp, a, -1)
+int in6_addr_flags(const char *, const struct in6_addr *);
int if_route6(const struct rt6 *rt, int);
#define add_route6(rt) if_route6(rt, 1)
#define change_route6(rt) if_route6(rt, 0)
#define del_route6(rt) if_route6(rt, -1)
#define del_src_route6(rt) if_route6(rt, -2);
+int pfx_flush(void);
int open_link_socket(void);
int manage_link(int);