+ __interface_set_down(iface, true);
+}
+
+void
+interface_add_user(struct interface_user *dep, struct interface *iface)
+{
+ if (!iface) {
+ list_add(&dep->list, &iface_all_users);
+ return;
+ }
+
+ dep->iface = iface;
+ list_add(&dep->list, &iface->users);
+ if (iface->state == IFS_UP)
+ dep->cb(dep, iface, IFEV_UP);
+}
+
+void
+interface_remove_user(struct interface_user *dep)
+{
+ list_del_init(&dep->list);
+ dep->iface = NULL;
+}
+
+static void
+interface_add_assignment_classes(struct interface *iface, struct blob_attr *list)
+{
+ struct blob_attr *cur;
+ int rem;
+
+ blobmsg_for_each_attr(cur, list, rem) {
+ if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
+ continue;
+
+ if (!blobmsg_check_attr(cur, NULL))
+ continue;
+
+ struct interface_assignment_class *c = malloc(sizeof(*c) + blobmsg_data_len(cur));
+ memcpy(c->name, blobmsg_data(cur), blobmsg_data_len(cur));
+ list_add(&c->head, &iface->assignment_classes);
+ }
+}
+
+static void
+interface_clear_assignment_classes(struct interface *iface)
+{
+ while (!list_empty(&iface->assignment_classes)) {
+ struct interface_assignment_class *c = list_first_entry(&iface->assignment_classes,
+ struct interface_assignment_class, head);
+ list_del(&c->head);
+ free(c);
+ }
+}
+
+static void
+interface_merge_assignment_data(struct interface *old, struct interface *new)
+{
+ bool changed = (old->assignment_hint != new->assignment_hint ||
+ old->assignment_length != new->assignment_length ||
+ old->assignment_iface_id_selection != new->assignment_iface_id_selection ||
+ (old->assignment_iface_id_selection == IFID_FIXED &&
+ memcmp(&old->assignment_fixed_iface_id, &new->assignment_fixed_iface_id,
+ sizeof(old->assignment_fixed_iface_id))) ||
+ list_empty(&old->assignment_classes) != list_empty(&new->assignment_classes));
+
+ struct interface_assignment_class *c;
+ list_for_each_entry(c, &new->assignment_classes, head) {
+ // Compare list entries one-by-one to see if there was a change
+ if (list_empty(&old->assignment_classes)) // The new list is longer
+ changed = true;
+
+ if (changed)
+ break;
+
+ struct interface_assignment_class *c_old = list_first_entry(&old->assignment_classes,
+ struct interface_assignment_class, head);
+
+ if (strcmp(c_old->name, c->name)) // An entry didn't match
+ break;
+
+ list_del(&c_old->head);
+ free(c_old);
+ }
+
+ // The old list was longer than the new one or the last entry didn't match
+ if (!list_empty(&old->assignment_classes)) {
+ interface_clear_assignment_classes(old);
+ changed = true;
+ }
+
+ list_splice_init(&new->assignment_classes, &old->assignment_classes);
+
+ if (changed) {
+ old->assignment_hint = new->assignment_hint;
+ old->assignment_length = new->assignment_length;
+ old->assignment_iface_id_selection = new->assignment_iface_id_selection;
+ old->assignment_fixed_iface_id = new->assignment_fixed_iface_id;
+ interface_refresh_assignments(true);
+ }
+}
+
+static void
+interface_alias_cb(struct interface_user *dep, struct interface *iface, enum interface_event ev)
+{
+ struct interface *alias = container_of(dep, struct interface, parent_iface);
+ struct device *dev = iface->l3_dev.dev;
+
+ switch (ev) {
+ case IFEV_UP:
+ if (!dev)
+ return;
+
+ interface_set_main_dev(alias, dev);
+ interface_set_available(alias, true);
+ break;
+ case IFEV_DOWN:
+ interface_set_available(alias, false);
+ interface_set_main_dev(alias, NULL);
+ break;
+ case IFEV_FREE:
+ interface_remove_user(dep);
+ break;
+ case IFEV_RELOAD:
+ case IFEV_UPDATE:
+ break;
+ }
+}
+
+static void
+interface_set_device_config(struct interface *iface, struct device *dev)
+{
+ if (!dev || !dev->default_config)
+ return;
+
+ if (!iface->device_config &&
+ (!dev->iface_config || dev->config_iface != iface))
+ return;
+
+ dev->config_iface = iface;
+ dev->iface_config = iface->device_config;
+ device_apply_config(dev, dev->type, iface->config);
+}
+
+static void
+interface_claim_device(struct interface *iface)
+{
+ struct interface *parent;
+ struct device *dev = NULL;
+
+ if (iface->parent_iface.iface)
+ interface_remove_user(&iface->parent_iface);
+
+ if (iface->parent_ifname) {
+ parent = vlist_find(&interfaces, iface->parent_ifname, parent, node);
+ iface->parent_iface.cb = interface_alias_cb;
+ interface_add_user(&iface->parent_iface, parent);
+ } else if (iface->ifname &&
+ !(iface->proto_handler->flags & PROTO_FLAG_NODEV)) {
+ dev = device_get(iface->ifname, true);
+ interface_set_device_config(iface, dev);
+ } else {
+ dev = iface->ext_dev.dev;
+ }
+
+ if (dev)
+ interface_set_main_dev(iface, dev);
+
+ if (iface->proto_handler->flags & PROTO_FLAG_INIT_AVAILABLE)
+ interface_set_available(iface, true);
+}
+
+static void
+interface_cleanup_state(struct interface *iface)
+{
+ interface_set_available(iface, false);
+
+ interface_flush_state(iface);
+ interface_clear_errors(iface);
+ interface_set_proto_state(iface, NULL);
+
+ interface_set_main_dev(iface, NULL);
+ interface_set_l3_dev(iface, NULL);
+}
+
+static void
+interface_cleanup(struct interface *iface)
+{
+ struct interface_user *dep, *tmp;
+
+ uloop_timeout_cancel(&iface->remove_timer);
+ device_remove_user(&iface->ext_dev);
+
+ if (iface->parent_iface.iface)
+ interface_remove_user(&iface->parent_iface);
+
+ list_for_each_entry_safe(dep, tmp, &iface->users, list)
+ interface_remove_user(dep);
+
+ interface_clear_assignment_classes(iface);
+ interface_ip_flush(&iface->config_ip);
+ interface_cleanup_state(iface);
+}
+
+static void
+interface_do_free(struct interface *iface)
+{
+ interface_event(iface, IFEV_FREE);
+ interface_cleanup(iface);
+ free(iface->config);
+ netifd_ubus_remove_interface(iface);
+ avl_delete(&interfaces.avl, &iface->node.avl);
+ free(iface);
+}
+
+static void
+interface_do_reload(struct interface *iface)
+{
+ interface_event(iface, IFEV_RELOAD);
+ interface_cleanup_state(iface);
+ proto_init_interface(iface, iface->config);
+ interface_claim_device(iface);
+}
+
+static void
+interface_handle_config_change(struct interface *iface)
+{
+ enum interface_config_state state = iface->config_state;
+
+ iface->config_state = IFC_NORMAL;
+ switch(state) {
+ case IFC_NORMAL:
+ break;
+ case IFC_RELOAD:
+ interface_do_reload(iface);
+ break;
+ case IFC_REMOVE:
+ interface_do_free(iface);
+ return;
+ }
+ if (iface->autostart && iface->available)
+ interface_set_up(iface);
+}
+
+static void
+interface_proto_cb(struct interface_proto_state *state, enum interface_proto_event ev)
+{
+ struct interface *iface = state->iface;
+
+ switch (ev) {
+ case IFPEV_UP:
+ if (iface->state != IFS_SETUP) {
+ interface_event(iface, IFEV_UPDATE);
+ return;
+ }
+
+ if (!iface->l3_dev.dev)
+ interface_set_l3_dev(iface, iface->main_dev.dev);
+
+ interface_ip_set_enabled(&iface->config_ip, true);
+ system_flush_routes();
+ iface->state = IFS_UP;
+ iface->start_time = system_get_rtime();
+ interface_event(iface, IFEV_UP);
+ netifd_log_message(L_NOTICE, "Interface '%s' is now up\n", iface->name);
+ break;
+ case IFPEV_DOWN:
+ if (iface->state == IFS_DOWN)
+ return;
+
+ netifd_log_message(L_NOTICE, "Interface '%s' is now down\n", iface->name);
+ mark_interface_down(iface);
+ if (iface->main_dev.dev)
+ device_release(&iface->main_dev);
+ if (iface->l3_dev.dev)
+ device_remove_user(&iface->l3_dev);
+ interface_handle_config_change(iface);
+ break;
+ case IFPEV_LINK_LOST:
+ if (iface->state != IFS_UP)
+ return;
+
+ netifd_log_message(L_NOTICE, "Interface '%s' has lost the connection\n", iface->name);
+ mark_interface_down(iface);
+ iface->state = IFS_SETUP;
+ break;
+ default:
+ return;
+ }
+
+ interface_write_resolv_conf();
+}
+
+void interface_set_proto_state(struct interface *iface, struct interface_proto_state *state)
+{
+ if (iface->proto) {
+ iface->proto->free(iface->proto);
+ iface->proto = NULL;
+ }
+ iface->state = IFS_DOWN;
+ iface->proto = state;
+ if (!state)
+ return;
+
+ state->proto_event = interface_proto_cb;
+ state->iface = iface;