+ bool _enabled = enable_route(ip, route_new);
+
+ if (!(route_new->flags & DEVROUTE_METRIC))
+ route_new->metric = iface->metric;
+
+ if (!(route_new->flags & DEVADDR_EXTERNAL) && !keep && _enabled)
+ system_add_route(dev, route_new);
+
+ route_new->iface = iface;
+ route_new->enabled = _enabled;
+ }
+}
+
+static void
+interface_update_host_route(struct vlist_tree *tree,
+ struct vlist_node *node_new,
+ struct vlist_node *node_old)
+{
+ struct interface *iface;
+ struct device *dev;
+ struct device_route *route_old, *route_new;
+
+ iface = container_of(tree, struct interface, host_routes);
+ dev = iface->l3_dev.dev;
+
+ route_old = container_of(node_old, struct device_route, node);
+ route_new = container_of(node_new, struct device_route, node);
+
+ if (node_old) {
+ system_del_route(dev, route_old);
+ free(route_old);
+ }
+
+ if (node_new)
+ system_add_route(dev, route_new);
+}
+
+
+static void
+interface_set_prefix_address(struct interface *iface, bool add,
+ struct device_prefix_assignment *assignment)
+{
+ struct interface *uplink = assignment->prefix->iface;
+ if (!iface->l3_dev.dev)
+ return;
+
+ struct device *l3_downlink = iface->l3_dev.dev;
+
+ struct device_addr addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.addr.in6 = assignment->addr;
+ addr.mask = assignment->length;
+ addr.flags = DEVADDR_INET6;
+ addr.preferred_until = assignment->prefix->preferred_until;
+ addr.valid_until = assignment->prefix->valid_until;
+
+ if (!add) {
+ if (assignment->enabled)
+ system_del_address(l3_downlink, &addr);
+ } else {
+ system_add_address(l3_downlink, &addr);
+
+ if (uplink && uplink->l3_dev.dev) {
+ int mtu = system_update_ipv6_mtu(
+ uplink->l3_dev.dev, 0);
+ if (mtu > 0)
+ system_update_ipv6_mtu(l3_downlink, mtu);
+ }
+ }
+ assignment->enabled = add;
+}
+
+
+static void
+interface_update_prefix_assignments(struct vlist_tree *tree,
+ struct vlist_node *node_new,
+ struct vlist_node *node_old)
+{
+ struct device_prefix_assignment *old, *new;
+ old = container_of(node_old, struct device_prefix_assignment, node);
+ new = container_of(node_new, struct device_prefix_assignment, node);
+
+ // Assignments persist across interface reloads etc.
+ // so use indirection to avoid dangling pointers
+ struct interface *iface = vlist_find(&interfaces,
+ (node_new) ? new->name : old->name, iface, node);
+
+ if (node_old && node_new) {
+ new->addr = old->addr;
+ new->length = old->length;
+ } else if (node_old) {
+ if (iface)
+ interface_set_prefix_address(iface, false, old);
+ free(old);
+ } else if (node_new) {
+ struct device_prefix *prefix = new->prefix;
+ uint64_t want = 1ULL << (64 - new->length);
+ prefix->avail &= ~(want - 1);
+ prefix->avail -= want;
+
+ // Invert assignment
+ uint64_t assigned = ~prefix->avail;
+ assigned &= (1ULL << (64 - prefix->length)) - 1;
+ assigned &= ~(want - 1);
+
+ // Assignment
+ new->addr = prefix->addr;
+ new->addr.s6_addr32[0] |=
+ htonl(assigned >> 32);
+ new->addr.s6_addr32[1] |=
+ htonl(assigned & 0xffffffffU);
+ new->addr.s6_addr[15] += 1;
+ }
+
+ if (node_new && (iface->state == IFS_UP || iface->state == IFS_SETUP))
+ interface_set_prefix_address(iface, true, new);
+}
+
+
+void
+interface_ip_set_prefix_assignment(struct device_prefix *prefix,
+ struct interface *iface, uint8_t length)
+{
+ struct device_prefix_assignment *assignment;
+
+ if (!length || length > 64) {
+ assignment = vlist_find(prefix->assignments, &iface, assignment, node);
+ if (assignment)
+ interface_set_prefix_address(iface, false, assignment);
+ } else {
+ uint8_t length = iface->proto_ip.assignment_length;
+ uint64_t want = 1ULL << (64 - length);
+ char *name;
+
+ if (prefix->avail < want && prefix->avail > 0) {
+ do {
+ want = 1ULL << (64 - ++length);
+ } while (want > prefix->avail);
+ }
+
+ if (prefix->avail < want)
+ return;
+
+ assignment = calloc_a(sizeof(*assignment),
+ &name, strlen(iface->name) + 1);
+ assignment->prefix = prefix;
+ assignment->length = length;
+ assignment->name = strcpy(name, iface->name);
+
+ vlist_add(prefix->assignments, &assignment->node, assignment->name);
+ }
+}
+
+static void
+interface_update_prefix(struct vlist_tree *tree,
+ struct vlist_node *node_new,
+ struct vlist_node *node_old)
+{
+ struct device_prefix *prefix_old, *prefix_new;
+ prefix_old = container_of(node_old, struct device_prefix, node);
+ prefix_new = container_of(node_new, struct device_prefix, node);
+
+ struct device_route route;
+ memset(&route, 0, sizeof(route));
+ route.flags = DEVADDR_INET6;
+ route.metric = INT32_MAX;
+ route.mask = (node_new) ? prefix_new->length : prefix_old->length;
+ route.addr.in6 = (node_new) ? prefix_new->addr : prefix_old->addr;
+
+ if (node_old && node_new) {
+ prefix_new->avail = prefix_old->avail;
+ prefix_new->assignments = prefix_old->assignments;
+ prefix_old->assignments = NULL;
+
+ // Update all assignments
+ struct device_prefix_assignment *assignment;
+ struct vlist_tree *assignments = prefix_new->assignments;
+ vlist_for_each_element(assignments, assignment, node)
+ assignments->update(assignments,
+ &assignment->node, &assignment->node);
+ } else if (node_new) {
+ prefix_new->avail = 1ULL << (64 - prefix_new->length);
+ prefix_new->assignments = calloc(1, sizeof(*prefix_new->assignments));
+ vlist_init(prefix_new->assignments, avl_strcmp,
+ interface_update_prefix_assignments);
+
+ // Create initial assignments for interfaces
+ struct interface *iface;
+ vlist_for_each_element(&interfaces, iface, node)
+ interface_ip_set_prefix_assignment(prefix_new, iface,
+ iface->proto_ip.assignment_length);
+
+ list_add(&prefix_new->head, &prefixes);
+
+ // Set null-route to avoid routing loops
+ system_add_route(NULL, &route);
+ }
+
+ if (node_old) {
+ // Remove null-route
+ system_del_route(NULL, &route);
+
+ list_del(&prefix_old->head);
+
+ if (prefix_old->assignments) {
+ vlist_flush_all(prefix_old->assignments);
+ free(prefix_old->assignments);
+ }
+ free(prefix_old);