Fix a spurious segfault when tearing down interfaces
[project/netifd.git] / interface.c
index f0fd43f..6ca1d0d 100644 (file)
@@ -43,6 +43,7 @@ enum {
        IFACE_ATTR_IP6TABLE,
        IFACE_ATTR_IP6CLASS,
        IFACE_ATTR_DELEGATE,
+       IFACE_ATTR_FORCE_LINK,
        IFACE_ATTR_MAX
 };
 
@@ -62,6 +63,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = {
        [IFACE_ATTR_IP6TABLE] = { .name = "ip6table", .type = BLOBMSG_TYPE_STRING },
        [IFACE_ATTR_IP6CLASS] = { .name = "ip6class", .type = BLOBMSG_TYPE_ARRAY },
        [IFACE_ATTR_DELEGATE] = { .name = "delegate", .type = BLOBMSG_TYPE_BOOL },
+       [IFACE_ATTR_FORCE_LINK] = { .name = "force_link", .type = BLOBMSG_TYPE_BOOL },
 };
 
 static const struct uci_blob_param_info iface_attr_info[IFACE_ATTR_MAX] = {
@@ -222,11 +224,14 @@ mark_interface_down(struct interface *iface)
 void
 __interface_set_down(struct interface *iface, bool force)
 {
-       switch (iface->state) {
+       enum interface_state state = iface->state;
+       switch (state) {
        case IFS_UP:
-               interface_event(iface, IFEV_DOWN);
        case IFS_SETUP:
                iface->state = IFS_TEARDOWN;
+               if (state == IFS_UP)
+                       interface_event(iface, IFEV_DOWN);
+
                interface_proto_event(iface->proto, PROTO_CMD_TEARDOWN, force);
                if (force)
                        interface_flush_state(iface);
@@ -262,15 +267,17 @@ __interface_set_up(struct interface *iface)
 static void
 interface_check_state(struct interface *iface)
 {
+       bool link_state = iface->link_state || iface->force_link;
+
        switch (iface->state) {
        case IFS_UP:
-               if (!iface->enabled || !iface->link_state) {
+               if (!iface->enabled || !link_state) {
                        mark_interface_down(iface);
                        interface_proto_event(iface->proto, PROTO_CMD_TEARDOWN, false);
                }
                break;
        case IFS_DOWN:
-               if (iface->enabled && iface->link_state && !config_init)
+               if (iface->autostart && iface->enabled && link_state && !config_init)
                        __interface_set_up(iface);
                break;
        default:
@@ -312,7 +319,7 @@ interface_cb(struct device_user *dep, enum device_event ev)
                new_state = true;
        case DEV_EVENT_REMOVE:
                interface_set_available(iface, new_state);
-               if (!new_state && dep->dev->external)
+               if (!new_state && dep->dev && dep->dev->external)
                        interface_set_main_dev(iface, NULL);
                break;
        case DEV_EVENT_UP:
@@ -325,6 +332,9 @@ interface_cb(struct device_user *dep, enum device_event ev)
         case DEV_EVENT_LINK_DOWN:
                interface_set_link_state(iface, new_state);
                break;
+       case DEV_EVENT_TOPO_CHANGE:
+               interface_proto_event(iface->proto, PROTO_CMD_RENEW, false);
+               return;
        default:
                break;
        }
@@ -571,6 +581,9 @@ interface_proto_cb(struct interface_proto_state *state, enum interface_proto_eve
                        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;
@@ -586,6 +599,8 @@ interface_proto_cb(struct interface_proto_state *state, enum interface_proto_eve
                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:
@@ -596,6 +611,8 @@ interface_proto_cb(struct interface_proto_state *state, enum interface_proto_eve
                mark_interface_down(iface);
                iface->state = IFS_SETUP;
                break;
+       default:
+               return;
        }
 
        interface_write_resolv_conf();
@@ -646,6 +663,7 @@ interface_alloc(const char *name, struct blob_attr *config)
        proto_attach_interface(iface, proto_name);
 
        iface->autostart = blobmsg_get_bool_default(tb[IFACE_ATTR_AUTO], true);
+       iface->force_link = blobmsg_get_bool_default(tb[IFACE_ATTR_FORCE_LINK], false);
        iface->proto_ip.no_defaultroute =
                !blobmsg_get_bool_default(tb[IFACE_ATTR_DEFAULTROUTE], true);
        iface->proto_ip.no_dns =
@@ -757,15 +775,12 @@ interface_set_l3_dev(struct interface *iface, struct device *dev)
 void
 interface_set_main_dev(struct interface *iface, struct device *dev)
 {
-       bool set_l3 = (!dev || iface->main_dev.dev == iface->l3_dev.dev);
        bool claimed = iface->l3_dev.claimed;
 
        if (iface->main_dev.dev == dev)
                return;
 
-       if (set_l3)
-               interface_set_l3_dev(iface, dev);
-
+       interface_set_available(iface, false);
        device_add_user(&iface->main_dev, dev);
        if (!dev) {
                interface_set_link_state(iface, false);
@@ -793,7 +808,7 @@ interface_remove_link(struct interface *iface, struct device *dev)
        if (dev != iface->main_dev.dev)
                return UBUS_STATUS_INVALID_ARGUMENT;
 
-       device_remove_user(&iface->main_dev);
+       interface_set_main_dev(iface, NULL);
        return 0;
 }
 
@@ -839,7 +854,7 @@ interface_handle_link(struct interface *iface, const char *name, bool add)
                if (iface->device_config)
                        device_set_config(dev, &simple_device_type, iface->config);
 
-               system_if_apply_settings(dev, &dev->settings);
+               system_if_apply_settings(dev, &dev->settings, dev->settings.flags);
                ret = interface_add_link(iface, dev);
        } else {
                ret = interface_remove_link(iface, dev);
@@ -976,6 +991,7 @@ interface_change_config(struct interface *if_old, struct interface *if_new)
        if_old->ifname = if_new->ifname;
        if_old->parent_ifname = if_new->parent_ifname;
        if_old->proto_handler = if_new->proto_handler;
+       if_old->force_link = if_new->force_link;
 
        if_old->proto_ip.no_dns = if_new->proto_ip.no_dns;
        interface_replace_dns(&if_old->config_ip, &if_new->config_ip);
@@ -1004,6 +1020,8 @@ interface_change_config(struct interface *if_old, struct interface *if_new)
        }
 
        interface_write_resolv_conf();
+       if (if_old->main_dev.dev)
+               interface_check_state(if_old);
 
 out:
        if_new->config = NULL;