/**
diff --git a/include/net/switchdev.h b/include/net/switchdev.h
index 89da893..7186fea 100644
--- a/include/net/switchdev.h
+++ b/include/net/switchdev.h
@@ -129,6 +129,10 @@ int switchdev_port_attr_get(struct net_device *dev,
struct switchdev_attr *attr);
int switchdev_port_attr_set(struct net_device *dev,
struct switchdev_attr *attr);
+int netdev_switch_ageing_set(struct net_device *dev, int ageing_interval);
+int netdev_switch_ageing_get(struct sk_buff *skb,
+ const struct net_device *dev,
+ struct netlink_callback *cb);
int switchdev_port_obj_add(struct net_device *dev, struct switchdev_obj *obj);
int switchdev_port_obj_del(struct net_device *dev, struct switchdev_obj *obj);
int switchdev_port_obj_dump(struct net_device *dev, struct switchdev_obj
*obj);
@@ -163,6 +167,17 @@ void switchdev_port_fwd_mark_set(struct net_device *dev,
#else
+static inline int netdev_switch_ageing_set(struct net_device *dev,
+ int ageing_interval)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int netdev_switch_ageing_get(struct net_device *dev)
+{
+ return -EOPNOTSUPP;
+}
+
static inline int switchdev_port_attr_get(struct net_device *dev,
struct switchdev_attr *attr)
{
diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
index 3635b77..a32ab4d 100644
--- a/include/uapi/linux/if_bridge.h
+++ b/include/uapi/linux/if_bridge.h
@@ -199,4 +199,23 @@ enum {
};
#define MDBA_SET_ENTRY_MAX (__MDBA_SET_ENTRY_MAX - 1)
+struct admsg {
+ __u8 adm_family;
+ __u8 adm_pad1;
+ __u16 adm_pad2;
+ __s32 adm_ifindex;
+ __u16 adm_ageing_interval;
+};
+
+/* The value of this macro is based on the value recommended by IEEE
+ * standard 802.1d.
+ */
+#define MIN_AGEING_INTERVAL_SECS (10)
+
+/* The value of DEFAULT_AGEING_INTERVAL_SECS is the default ageing
+ * interval that was used in br_device.c. This default value is also
+ * recommended by IEEE Standard 802.1d.
+ */
+#define DEFAULT_AGEING_INTERVAL_SECS (300)
+
#endif /* _UAPI_LINUX_IF_BRIDGE_H */
diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
index 47d24cb..9321818 100644
--- a/include/uapi/linux/rtnetlink.h
+++ b/include/uapi/linux/rtnetlink.h
@@ -139,6 +139,13 @@ enum {
RTM_GETNSID = 90,
#define RTM_GETNSID RTM_GETNSID
+ RTM_SETAGEING = 92,
+#define RTM_SETAGEING RTM_SETAGEING
+ RTM_SETDEFAULTAGEING = 93,
+#define RTM_SETDEFAULTAGEING RTM_SETDEFAULTAGEING
+ RTM_GETAGEING = 94,
+#define RTM_GETAGEING RTM_GETAGEING
+
__RTM_MAX,
#define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1)
};
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index 4ff77a1..2c81190 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -18,6 +18,7 @@
#include <linux/ethtool.h>
#include <linux/list.h>
#include <linux/netfilter_bridge.h>
+#include <net/netlink.h>
#include <asm/uaccess.h>
#include "br_private.h"
@@ -309,6 +310,43 @@ static int br_del_slave(struct net_device *dev, struct
net_device *slave_dev)
return br_del_if(br, slave_dev);
}
+static int br_setageing(const struct net_device *dev, int ageing_interval)
+{
+ struct net_bridge *br = netdev_priv(dev);
+
+ return br_fdb_setageing(br, ageing_interval);
+}
+
+static int br_getageing(const struct net_device *dev)
+{
+ struct net_bridge *br = netdev_priv(dev);
+
+ return br->ageing_time / HZ;
+}
+
+static int br_getageing_nl(struct sk_buff *skb,
+ const struct net_device *dev,
+ struct netlink_callback *cb)
+{
+ struct nlmsghdr *nlhdr;
+ struct admsg *amsg;
+
+ struct net_bridge *br = netdev_priv(dev);
+
+ nlhdr = nlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, RTM_GETAGEING, sizeof(*amsg), 0);
+ if (!nlhdr)
+ return -EMSGSIZE;
+
+ amsg = nlmsg_data(nlhdr);
+
+ amsg->adm_family = AF_BRIDGE;
+ amsg->adm_ifindex = dev->ifindex;
+ amsg->adm_ageing_interval = br->ageing_time / HZ;
+
+ return 0;
+}
+
static const struct ethtool_ops br_ethtool_ops = {
.get_drvinfo = br_getinfo,
.get_link = ethtool_op_get_link,
@@ -339,6 +377,9 @@ static const struct net_device_ops br_netdev_ops = {
.ndo_bridge_getlink = br_getlink,
.ndo_bridge_setlink = br_setlink,
.ndo_bridge_dellink = br_dellink,
+ .ndo_bridge_setageing = br_setageing,
+ .ndo_bridge_getageing_nl = br_getageing_nl,
+ .ndo_bridge_getageing = br_getageing,
};
static void br_dev_free(struct net_device *dev)
@@ -391,7 +432,7 @@ void br_dev_setup(struct net_device *dev)
br->bridge_max_age = br->max_age = 20 * HZ;
br->bridge_hello_time = br->hello_time = 2 * HZ;
br->bridge_forward_delay = br->forward_delay = 15 * HZ;
- br->ageing_time = 300 * HZ;
+ br->ageing_time = DEFAULT_AGEING_INTERVAL_SECS * HZ;
br_netfilter_rtable_init(br);
br_stp_timer_init(br);
diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
index 9e9875d..c46f734 100644
--- a/net/bridge/br_fdb.c
+++ b/net/bridge/br_fdb.c
@@ -283,6 +283,25 @@ out:
spin_unlock_bh(&br->hash_lock);
}
+int br_fdb_setageing(struct net_bridge *br, int ageing_interval)
+{
+ unsigned long next_timer;
+ unsigned long exp_time;
+
+ if (ageing_interval < MIN_AGEING_INTERVAL_SECS)
+ return -EINVAL;
+
+ br->ageing_time = ageing_interval * HZ;
+
+ next_timer = jiffies + hold_time(br);
+ exp_time = br->gc_timer.expires;
+
+ if (exp_time > next_timer)
+ mod_timer(&br->gc_timer, round_jiffies_up(next_timer));
+
+ return 0;
+}
+
void br_fdb_cleanup(unsigned long _data)
{
struct net_bridge *br = (struct net_bridge *)_data;
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 3ad1290..b745e06 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -410,6 +410,7 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct
net_bridge_port *p,
const unsigned char *addr, u16 vid);
int br_fdb_external_learn_del(struct net_bridge *br, struct net_bridge_port
*p,
const unsigned char *addr, u16 vid);
+int br_fdb_setageing(struct net_bridge *br, int ageing_interval);
/* br_forward.c */
void br_deliver(const struct net_bridge_port *to, struct sk_buff *skb);
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 5fb4af2..e7c6197 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -3277,6 +3277,128 @@ out:
return err;
}
+static int configure_ageing_interval(struct net_device *dev, int interval)
+{
+ int err = -EOPNOTSUPP;
+ int old_ageing_timer = -1;
+ const struct net_device_ops *ops = dev->netdev_ops;
+
+ if (ops->ndo_bridge_getageing)
+ old_ageing_timer = ops->ndo_bridge_getageing(dev);
+
+ if (ops->ndo_bridge_setageing)
+ err = ops->ndo_bridge_setageing(dev, interval);
+
+ if (!err) {
+ err = netdev_switch_ageing_set(dev, interval);
+
+ if (err == -EOPNOTSUPP)
+ return 0;
+
+ if (err && (old_ageing_timer >= MIN_AGEING_INTERVAL_SECS))
+ ops->ndo_bridge_setageing(dev, old_ageing_timer);
+ }
+
+ return err;
+}
+
+static int rtnl_bridge_setageing(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+ struct net *net = sock_net(skb->sk);
+ struct admsg *adm;
+ struct net_device *dev;
+ int err;
+ struct nlattr *tb[NDA_MAX + 1];
+
+ err = nlmsg_parse(nlh, sizeof(*adm), tb, NDA_MAX, NULL);
+ if (err < 0)
+ return err;
+
+ adm = nlmsg_data(nlh);
+ if (adm->adm_ifindex == 0)
+ return -EINVAL;
+
+ dev = __dev_get_by_index(net, adm->adm_ifindex);
+ if (!dev)
+ return -ENODEV;
+
+ err = -EOPNOTSUPP;
+
+ if (!(dev->priv_flags & IFF_EBRIDGE))
+ return err;
+
+ err = configure_ageing_interval(dev, adm->adm_ageing_interval);
+
+ return err;
+}
+
+static int rtnl_bridge_setdefaultageing(struct sk_buff *skb,
+ struct nlmsghdr *nlh)
+{
+ struct net *net = sock_net(skb->sk);
+ struct admsg *adm;
+ struct net_device *dev;
+ int err;
+ struct nlattr *tb[NDA_MAX + 1];
+
+ err = nlmsg_parse(nlh, sizeof(*adm), tb, NDA_MAX, NULL);
+ if (err < 0)
+ return err;
+
+ adm = nlmsg_data(nlh);
+ if (adm->adm_ifindex == 0)
+ return -EINVAL;
+
+ dev = __dev_get_by_index(net, adm->adm_ifindex);
+ if (!dev)
+ return -ENODEV;
+
+ err = -EOPNOTSUPP;
+
+ if (!(dev->priv_flags & IFF_EBRIDGE))
+ return err;
+
+ err = configure_ageing_interval(dev, DEFAULT_AGEING_INTERVAL_SECS);
+
+ return err;
+}
+
+static int rtnl_bridge_getageing(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct net *net = sock_net(skb->sk);
+ struct admsg *adm;
+ struct net_device *dev;
+ int err;
+ struct nlattr *tb[NDA_MAX + 1];
+
+ err = nlmsg_parse(cb->nlh, sizeof(*adm), tb, NDA_MAX, NULL);
+ if (err < 0)
+ return err;
+
+ adm = nlmsg_data(cb->nlh);
+ if (adm->adm_ifindex == 0)
+ return -EINVAL;
+
+ dev = __dev_get_by_index(net, adm->adm_ifindex);
+ if (!dev)
+ return -ENODEV;
+
+ err = -EOPNOTSUPP;
+
+ if (!(dev->priv_flags & IFF_EBRIDGE))
+ return err;
+
+ if (dev->netdev_ops->ndo_bridge_getageing_nl)
+ err = dev->netdev_ops->ndo_bridge_getageing_nl(skb, dev, cb);
+
+ if (err)
+ return err;
+
+ cb->args[0] = 1;
+ return 0;
+}
+
/* Process one rtnetlink message. */
static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
@@ -3427,5 +3549,11 @@ void __init rtnetlink_init(void)
rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, rtnl_bridge_getlink, NULL);
rtnl_register(PF_BRIDGE, RTM_DELLINK, rtnl_bridge_dellink, NULL, NULL);
rtnl_register(PF_BRIDGE, RTM_SETLINK, rtnl_bridge_setlink, NULL, NULL);
+ rtnl_register(PF_BRIDGE, RTM_GETAGEING, NULL,
+ rtnl_bridge_getageing, NULL);
+ rtnl_register(PF_BRIDGE, RTM_SETAGEING,
+ rtnl_bridge_setageing, NULL, NULL);
+ rtnl_register(PF_BRIDGE, RTM_SETDEFAULTAGEING,
+ rtnl_bridge_setdefaultageing, NULL, NULL);
}
diff --git a/net/switchdev/switchdev.c b/net/switchdev/switchdev.c
index 33bafa2..501e892 100644
--- a/net/switchdev/switchdev.c
+++ b/net/switchdev/switchdev.c
@@ -1142,3 +1142,34 @@ void switchdev_port_fwd_mark_set(struct net_device *dev,
dev->offload_fwd_mark = mark;
}
EXPORT_SYMBOL_GPL(switchdev_port_fwd_mark_set);
+
+int netdev_switch_ageing_set(struct net_device *dev,
+ int ageing_interval)
+{
+ int err = 0;
+
+ struct net_device *pd;
+ struct list_head *iter;
+
+ netdev_for_each_lower_dev(dev, pd, iter) {
+ if (!pd || !pd->netdev_ops ||
+ !pd->netdev_ops->ndo_bridge_setageing)
+ continue;
+
+ err = pd->netdev_ops->ndo_bridge_setageing(pd,
+ ageing_interval);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(netdev_switch_ageing_set);
+
+int netdev_switch_ageing_get(struct sk_buff *skb,
+ const struct net_device *dev,
+ struct netlink_callback *cb)
+{
+ return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL_GPL(netdev_switch_ageing_get);
diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c
index 2bbb418..e29836e 100644
--- a/security/selinux/nlmsgtab.c
+++ b/security/selinux/nlmsgtab.c
@@ -154,7 +154,7 @@ int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32
*perm)
switch (sclass) {
case SECCLASS_NETLINK_ROUTE_SOCKET:
/* RTM_MAX always point to RTM_SETxxxx, ie RTM_NEWxxx + 3 */
- BUILD_BUG_ON(RTM_MAX != (RTM_NEWNSID + 3));
+ BUILD_BUG_ON(RTM_MAX != (RTM_SETAGEING + 3));
err = nlmsg_perm(nlmsg_type, perm, nlmsg_route_perms,
sizeof(nlmsg_route_perms));
break;
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majord...@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html