device: add support for configuring devices with external auth handler
authorFelix Fietkau <nbd@nbd.name>
Mon, 17 May 2021 09:20:09 +0000 (11:20 +0200)
committerFelix Fietkau <nbd@nbd.name>
Mon, 17 May 2021 09:23:30 +0000 (11:23 +0200)
This can be used to support 802.1x on wired devices.
In order to use this, the device section for each port needing authentication
needs to contain the option auth 1
When set, this option prevents devices from being added to bridges or configured
with IP settings by default, until the set_state ubus call on network.device
sets "auth_status" to true for the device.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
bridge.c
device.c
device.h
interface.c
ubus.c

index 099dfe4d24ef957422e89d5ee333b2aa929363f9..397ac979daafd6fbe3a1a9c527a4eb7520856ebd 100644 (file)
--- a/bridge.c
+++ b/bridge.c
@@ -122,6 +122,7 @@ struct bridge_member {
        struct device_user dev;
        uint16_t pvid;
        bool present;
+       bool active;
        char name[];
 };
 
@@ -299,19 +300,21 @@ bridge_set_vlan_state(struct bridge_state *bst, struct bridge_vlan *vlan, bool a
 }
 
 static int
-bridge_disable_member(struct bridge_member *bm)
+bridge_disable_member(struct bridge_member *bm, bool keep_dev)
 {
        struct bridge_state *bst = bm->bst;
        struct bridge_vlan *vlan;
 
-       if (!bm->present)
+       if (!bm->present || !bm->active)
                return 0;
 
+       bm->active = false;
        vlist_for_each_element(&bst->dev.vlans, vlan, node)
                bridge_set_member_vlan(bm, vlan, false);
 
        system_bridge_delif(&bst->dev, bm->dev.dev);
-       device_release(&bm->dev);
+       if (!keep_dev)
+               device_release(&bm->dev);
 
        device_broadcast_event(&bst->dev, DEV_EVENT_TOPO_CHANGE);
 
@@ -356,6 +359,7 @@ bridge_enable_member(struct bridge_member *bm)
 {
        struct bridge_state *bst = bm->bst;
        struct bridge_vlan *vlan;
+       struct device *dev;
        int ret;
 
        if (!bm->present)
@@ -375,12 +379,20 @@ bridge_enable_member(struct bridge_member *bm)
        if (ret < 0)
                goto error;
 
+       dev = bm->dev.dev;
+       if (dev->settings.auth && !dev->auth_status)
+               return -1;
+
+       if (bm->active)
+               return 0;
+
        ret = system_bridge_addif(&bst->dev, bm->dev.dev);
        if (ret < 0) {
                D(DEVICE, "Bridge device %s could not be added\n", bm->dev.dev->ifname);
                goto error;
        }
 
+       bm->active = true;
        if (bst->has_vlans) {
                /* delete default VLAN 1 */
                system_bridge_vlan(bm->dev.dev->ifname, 1, false, 0);
@@ -412,7 +424,7 @@ bridge_remove_member(struct bridge_member *bm)
                return;
 
        if (bst->dev.active)
-               bridge_disable_member(bm);
+               bridge_disable_member(bm, false);
 
        bm->present = false;
        bm->bst->n_present--;
@@ -481,10 +493,11 @@ bridge_check_retry(struct bridge_state *bst)
 }
 
 static void
-bridge_member_cb(struct device_user *dev, enum device_event ev)
+bridge_member_cb(struct device_user *dep, enum device_event ev)
 {
-       struct bridge_member *bm = container_of(dev, struct bridge_member, dev);
+       struct bridge_member *bm = container_of(dep, struct bridge_member, dev);
        struct bridge_state *bst = bm->bst;
+       struct device *dev = dep->dev;
 
        switch (ev) {
        case DEV_EVENT_ADD:
@@ -495,19 +508,30 @@ bridge_member_cb(struct device_user *dev, enum device_event ev)
 
                if (bst->n_present == 1)
                        device_set_present(&bst->dev, true);
-               if (bst->dev.active && !bridge_enable_member(bm)) {
-                       /*
-                        * Adding a bridge member can overwrite the bridge mtu
-                        * in the kernel, apply the bridge settings in case the
-                        * bridge mtu is set
-                        */
-                       system_if_apply_settings(&bst->dev, &bst->dev.settings,
-                                                DEV_OPT_MTU | DEV_OPT_MTU6);
-               }
+               fallthrough;
+       case DEV_EVENT_AUTH_UP:
+               if (!bst->dev.active)
+                       break;
+
+               if (bridge_enable_member(bm))
+                       break;
+
+               /*
+                * Adding a bridge member can overwrite the bridge mtu
+                * in the kernel, apply the bridge settings in case the
+                * bridge mtu is set
+                */
+               system_if_apply_settings(&bst->dev, &bst->dev.settings,
+                                        DEV_OPT_MTU | DEV_OPT_MTU6);
+               break;
+       case DEV_EVENT_LINK_DOWN:
+               if (!dev->settings.auth)
+                       break;
 
+               bridge_disable_member(bm, true);
                break;
        case DEV_EVENT_REMOVE:
-               if (dev->hotplug) {
+               if (dep->hotplug) {
                        vlist_delete(&bst->members, &bm->node);
                        return;
                }
@@ -529,7 +553,7 @@ bridge_set_down(struct bridge_state *bst)
        bst->set_state(&bst->dev, false);
 
        vlist_for_each_element(&bst->members, bm, node)
-               bridge_disable_member(bm);
+               bridge_disable_member(bm, false);
 
        bridge_disable_interface(bst);
 
index 7f011b615fbf82bc9f3a9fd3b99a5ff2034a8322..26254cc2eb902a9b7b7a2aef97d998fbe3edbeff 100644 (file)
--- a/device.c
+++ b/device.c
@@ -59,6 +59,7 @@ static const struct blobmsg_policy dev_attrs[__DEV_ATTR_MAX] = {
        [DEV_ATTR_DROP_GRATUITOUS_ARP] = { .name = "drop_gratuitous_arp", .type = BLOBMSG_TYPE_BOOL },
        [DEV_ATTR_DROP_UNSOLICITED_NA] = { .name = "drop_unsolicited_na", .type = BLOBMSG_TYPE_BOOL },
        [DEV_ATTR_ARP_ACCEPT] = { .name = "arp_accept", .type = BLOBMSG_TYPE_BOOL },
+       [DEV_ATTR_AUTH] = { .name = "auth", .type = BLOBMSG_TYPE_BOOL },
 };
 
 const struct uci_blob_param_list device_attr_list = {
@@ -270,6 +271,7 @@ device_merge_settings(struct device *dev, struct device_settings *n)
                s->drop_unsolicited_na : os->drop_unsolicited_na;
        n->arp_accept = s->flags & DEV_OPT_ARP_ACCEPT ?
                s->arp_accept : os->arp_accept;
+       n->auth = s->flags & DEV_OPT_AUTH ? s->auth : os->auth;
        n->flags = s->flags | os->flags | os->valid_flags;
 }
 
@@ -439,6 +441,11 @@ device_init_settings(struct device *dev, struct blob_attr **tb)
                s->flags |= DEV_OPT_ARP_ACCEPT;
        }
 
+       if ((cur = tb[DEV_ATTR_AUTH])) {
+               s->auth = blobmsg_get_bool(cur);
+               s->flags |= DEV_OPT_AUTH;
+       }
+
        device_set_disabled(dev, disabled);
 }
 
@@ -716,6 +723,28 @@ device_refresh_present(struct device *dev)
        __device_set_present(dev, state);
 }
 
+void
+device_set_auth_status(struct device *dev, bool value)
+{
+       if (dev->auth_status == value)
+               return;
+
+       dev->auth_status = value;
+       if (!dev->present)
+               return;
+
+       if (dev->auth_status) {
+               device_broadcast_event(dev, DEV_EVENT_AUTH_UP);
+               return;
+       }
+
+       device_broadcast_event(dev, DEV_EVENT_LINK_DOWN);
+       if (!dev->link_active)
+               return;
+
+       device_broadcast_event(dev, DEV_EVENT_LINK_UP);
+}
+
 void device_set_present(struct device *dev, bool state)
 {
        if (dev->sys_present == state)
@@ -734,6 +763,8 @@ void device_set_link(struct device *dev, bool state)
        netifd_log_message(L_NOTICE, "%s '%s' link is %s\n", dev->type->name, dev->ifname, state ? "up" : "down" );
 
        dev->link_active = state;
+       if (!state)
+               dev->auth_status = false;
        device_broadcast_event(dev, state ? DEV_EVENT_LINK_UP : DEV_EVENT_LINK_DOWN);
 }
 
@@ -1091,6 +1122,7 @@ device_dump_status(struct blob_buf *b, struct device *dev)
 
        blobmsg_add_u8(b, "up", !!dev->active);
        blobmsg_add_u8(b, "carrier", !!dev->link_active);
+       blobmsg_add_u8(b, "auth_status", !!dev->auth_status);
 
        if (dev->type->dump_info)
                dev->type->dump_info(dev, b);
@@ -1157,6 +1189,8 @@ device_dump_status(struct blob_buf *b, struct device *dev)
                        blobmsg_add_u8(b, "drop_unsolicited_na", st.drop_unsolicited_na);
                if (st.flags & DEV_OPT_ARP_ACCEPT)
                        blobmsg_add_u8(b, "arp_accept", st.arp_accept);
+               if (st.flags & DEV_OPT_AUTH)
+                       blobmsg_add_u8(b, "auth", st.auth);
        }
 
        s = blobmsg_open_table(b, "statistics");
index f6eaf275e18ced17a4a4ab0e0b18fe313c755fa2..ed07791ba458d56632259d1e932dc4bc8fedf48b 100644 (file)
--- a/device.h
+++ b/device.h
@@ -59,6 +59,7 @@ enum {
        DEV_ATTR_DROP_GRATUITOUS_ARP,
        DEV_ATTR_DROP_UNSOLICITED_NA,
        DEV_ATTR_ARP_ACCEPT,
+       DEV_ATTR_AUTH,
        __DEV_ATTR_MAX,
 };
 
@@ -100,7 +101,7 @@ enum {
        DEV_OPT_MLDVERSION              = (1 << 8),
        DEV_OPT_NEIGHREACHABLETIME      = (1 << 9),
        DEV_OPT_DEFAULT_MACADDR         = (1 << 10),
-       /* 1 bit hole */
+       DEV_OPT_AUTH                    = (1 << 11),
        DEV_OPT_MTU6                    = (1 << 12),
        DEV_OPT_DADTRANSMITS            = (1 << 13),
        DEV_OPT_MULTICAST_TO_UNICAST    = (1 << 14),
@@ -134,6 +135,7 @@ enum device_event {
        DEV_EVENT_UP,
        DEV_EVENT_DOWN,
 
+       DEV_EVENT_AUTH_UP,
        DEV_EVENT_LINK_UP,
        DEV_EVENT_LINK_DOWN,
 
@@ -192,6 +194,7 @@ struct device_settings {
        bool drop_gratuitous_arp;
        bool drop_unsolicited_na;
        bool arp_accept;
+       bool auth;
 };
 
 /*
@@ -220,6 +223,7 @@ struct device {
        int active;
        /* DEV_EVENT_LINK_UP */
        bool link_active;
+       bool auth_status;
 
        bool external;
        bool disabled;
@@ -324,6 +328,8 @@ struct device *get_vlan_device_chain(const char *ifname, bool create);
 void alias_notify_device(const char *name, struct device *dev);
 struct device *device_alias_get(const char *name);
 
+void device_set_auth_status(struct device *dev, bool value);
+
 static inline void
 device_set_deferred(struct device *dev, bool value)
 {
@@ -338,6 +344,15 @@ device_set_disabled(struct device *dev, bool value)
        device_refresh_present(dev);
 }
 
+static inline bool
+device_link_active(struct device *dev)
+{
+       if (dev->settings.auth && !dev->auth_status)
+               return false;
+
+       return dev->link_active;
+}
+
 bool device_check_ip6segmentrouting(void);
 
 #endif
index 2a8f604207c3fdd1e47348274fce0428727906bf..a91246a1ae03769f16e798af7f063b6cf264d187 100644 (file)
@@ -99,6 +99,17 @@ interface_error_flush(struct interface *iface)
        }
 }
 
+static bool
+interface_force_link(struct interface *iface)
+{
+       struct device *dev = iface->main_dev.dev;
+
+       if (dev && dev->settings.auth)
+               return false;
+
+       return iface->force_link;
+}
+
 static void
 interface_clear_errors(struct interface *iface)
 {
@@ -344,7 +355,7 @@ __interface_set_up(struct interface *iface)
 static void
 interface_check_state(struct interface *iface)
 {
-       bool link_state = iface->link_state || iface->force_link;
+       bool link_state = iface->link_state || interface_force_link(iface);
 
        switch (iface->state) {
        case IFS_UP:
@@ -390,7 +401,8 @@ interface_set_link_state(struct interface *iface, bool new_state)
        iface->link_state = new_state;
        interface_check_state(iface);
 
-       if (new_state && iface->force_link && iface->state == IFS_UP && !iface->link_up_event) {
+       if (new_state && interface_force_link(iface) &&
+           iface->state == IFS_UP && !iface->link_up_event) {
                interface_event(iface, IFEV_LINK_UP);
                iface->link_up_event = true;
        }
@@ -424,11 +436,10 @@ interface_main_dev_cb(struct device_user *dep, enum device_event ev)
        case DEV_EVENT_DOWN:
                interface_set_enabled(iface, false);
                break;
+       case DEV_EVENT_AUTH_UP:
        case DEV_EVENT_LINK_UP:
-               interface_set_link_state(iface, true);
-               break;
        case DEV_EVENT_LINK_DOWN:
-               interface_set_link_state(iface, false);
+               interface_set_link_state(iface, device_link_active(dep->dev));
                break;
        case DEV_EVENT_TOPO_CHANGE:
                interface_proto_event(iface->proto, PROTO_CMD_RENEW, false);
diff --git a/ubus.c b/ubus.c
index 9098c662cc4144f5bf9eec6f86a334016c7832e2..be150626bc02c01c78443f440ee03fe58c7d7495 100644 (file)
--- a/ubus.c
+++ b/ubus.c
@@ -298,12 +298,14 @@ error:
 enum {
        DEV_STATE_NAME,
        DEV_STATE_DEFER,
+       DEV_STATE_AUTH_STATUS,
        __DEV_STATE_MAX,
 };
 
 static const struct blobmsg_policy dev_state_policy[__DEV_STATE_MAX] = {
        [DEV_STATE_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
        [DEV_STATE_DEFER] = { .name = "defer", .type = BLOBMSG_TYPE_BOOL },
+       [DEV_STATE_AUTH_STATUS] = { .name = "auth_status", .type = BLOBMSG_TYPE_BOOL },
 };
 
 static int
@@ -329,6 +331,10 @@ netifd_handle_set_state(struct ubus_context *ctx, struct ubus_object *obj,
        if (cur)
                device_set_deferred(dev, !!blobmsg_get_u8(cur));
 
+       cur = tb[DEV_STATE_AUTH_STATUS];
+       if (cur)
+               device_set_auth_status(dev, !!blobmsg_get_u8(cur));
+
        return 0;
 }