interface-ip: fix race condition in IPv6 prefix address generation
[project/netifd.git] / interface-ip.c
index ddca5d2..716a093 100644 (file)
@@ -566,8 +566,9 @@ interface_update_proto_addr(struct vlist_tree *tree,
                                a_old->preferred_until != a_new->preferred_until)
                        replace = true;
 
-               if ((a_new->flags & DEVADDR_FAMILY) == DEVADDR_INET4 &&
-                   a_new->broadcast != a_old->broadcast)
+               if (((a_new->flags & DEVADDR_FAMILY) == DEVADDR_INET4) &&
+                    (a_new->broadcast != a_old->broadcast ||
+                     a_new->point_to_point != a_old->point_to_point))
                        keep = false;
        }
 
@@ -760,15 +761,7 @@ interface_set_prefix_address(struct device_prefix_assignment *assignment,
        memset(&addr, 0, sizeof(addr));
        memset(&route, 0, sizeof(route));
 
-       if (IN6_IS_ADDR_UNSPECIFIED(&assignment->addr)) {
-               addr.addr.in6 = prefix->addr;
-               addr.addr.in6.s6_addr32[1] |= htonl(assignment->assigned);
-               generate_ifaceid(iface, &addr.addr.in6);
-               assignment->addr = addr.addr.in6;
-       }
-       else
-               addr.addr.in6 = assignment->addr;
-
+       addr.addr.in6 = assignment->addr;
        addr.mask = assignment->length;
        addr.flags = DEVADDR_INET6 | DEVADDR_OFFLINK;
        addr.preferred_until = prefix->preferred_until;
@@ -777,15 +770,18 @@ interface_set_prefix_address(struct device_prefix_assignment *assignment,
        route.flags = DEVADDR_INET6;
        route.mask = addr.mask < 64 ? 64 : addr.mask;
        route.addr = addr.addr;
-       clear_if_addr(&route.addr, route.mask);
-       interface_set_route_info(iface, &route);
 
        if (!add && assignment->enabled) {
                time_t now = system_get_rtime();
+
                addr.preferred_until = now;
                if (!addr.valid_until || addr.valid_until - now > 7200)
                        addr.valid_until = now + 7200;
 
+               if (iface->ip6table)
+                       set_ip_source_policy(false, true, IPRULE_PRIORITY_ADDR_MASK, &addr.addr,
+                                       addr.mask < 64 ? 64 : addr.mask, iface->ip6table, NULL, NULL, false);
+
                if (prefix->iface) {
                        if (prefix->iface->ip6table)
                                set_ip_source_policy(false, true, IPRULE_PRIORITY_NW, &addr.addr,
@@ -795,23 +791,43 @@ interface_set_prefix_address(struct device_prefix_assignment *assignment,
                                                        addr.mask, 0, iface, "unreachable", true);
                }
 
+               clear_if_addr(&route.addr, route.mask);
+               interface_set_route_info(iface, &route);
+
                system_del_route(l3_downlink, &route);
                system_add_address(l3_downlink, &addr);
 
                assignment->enabled = false;
-       } else if (add && (iface->state == IFS_UP || iface->state == IFS_SETUP) &&
-                       !system_add_address(l3_downlink, &addr)) {
+       } else if (add && (iface->state == IFS_UP || iface->state == IFS_SETUP)) {
+               if (IN6_IS_ADDR_UNSPECIFIED(&addr.addr.in6)) {
+                       addr.addr.in6 = prefix->addr;
+                       addr.addr.in6.s6_addr32[1] |= htonl(assignment->assigned);
+                       generate_ifaceid(iface, &addr.addr.in6);
+                       assignment->addr = addr.addr.in6;
+                       route.addr = addr.addr;
+               }
 
-               if (prefix->iface && !assignment->enabled) {
-                       set_ip_source_policy(true, true, IPRULE_PRIORITY_REJECT, &addr.addr,
-                                       addr.mask, 0, iface, "unreachable", true);
+               if (system_add_address(l3_downlink, &addr))
+                       return;
 
-                       if (prefix->iface->ip6table)
-                               set_ip_source_policy(true, true, IPRULE_PRIORITY_NW, &addr.addr,
-                                               addr.mask, prefix->iface->ip6table, iface, NULL, true);
+               if (!assignment->enabled) {
+                       if (iface->ip6table)
+                               set_ip_source_policy(true, true, IPRULE_PRIORITY_ADDR_MASK, &addr.addr,
+                                               addr.mask < 64 ? 64 : addr.mask, iface->ip6table, NULL, NULL, false);
+
+                       if (prefix->iface) {
+                               set_ip_source_policy(true, true, IPRULE_PRIORITY_REJECT, &addr.addr,
+                                               addr.mask, 0, iface, "unreachable", true);
+
+                               if (prefix->iface->ip6table)
+                                       set_ip_source_policy(true, true, IPRULE_PRIORITY_NW, &addr.addr,
+                                                       addr.mask, prefix->iface->ip6table, iface, NULL, true);
+                       }
                }
 
-               route.metric = iface->metric;
+               clear_if_addr(&route.addr, route.mask);
+               interface_set_route_info(iface, &route);
+
                system_add_route(l3_downlink, &route);
 
                if (uplink && uplink->l3_dev.dev && !(l3_downlink->settings.flags & DEV_OPT_MTU6)) {
@@ -1047,6 +1063,10 @@ interface_update_prefix(struct vlist_tree *tree,
                list_for_each_entry(c, &prefix_new->assignments, head)
                        if ((iface = vlist_find(&interfaces, c->name, iface, node)))
                                interface_set_prefix_address(c, prefix_new, iface, true);
+
+               if (prefix_new->preferred_until != prefix_old->preferred_until ||
+                               prefix_new->valid_until != prefix_old->valid_until)
+                       ip->iface->updated |= IUF_PREFIX;
        } else if (node_new) {
                // Set null-route to avoid routing loops
                system_add_route(NULL, &route);
@@ -1283,13 +1303,14 @@ __interface_write_dns_entries(FILE *f)
 
        avl_for_each_element(&resolv_conf_iface_entries, entry, node) {
                iface = (struct interface *)entry->node.key;
+               struct device *dev = iface->l3_dev.dev;
 
                fprintf(f, "# Interface %s\n", iface->name);
 
-               write_resolv_conf_entries(f, &iface->config_ip, iface->ifname);
+               write_resolv_conf_entries(f, &iface->config_ip, dev->ifname);
 
                if (!iface->proto_ip.no_dns)
-                       write_resolv_conf_entries(f, &iface->proto_ip, iface->ifname);
+                       write_resolv_conf_entries(f, &iface->proto_ip, dev->ifname);
        }
 
        avl_remove_all_elements(&resolv_conf_iface_entries, entry, node, n_entry)