+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