linux-brain/net/decnet/dn_fib.c
Johannes Berg 8cb081746c netlink: make validation more configurable for future strictness
We currently have two levels of strict validation:

 1) liberal (default)
     - undefined (type >= max) & NLA_UNSPEC attributes accepted
     - attribute length >= expected accepted
     - garbage at end of message accepted
 2) strict (opt-in)
     - NLA_UNSPEC attributes accepted
     - attribute length >= expected accepted

Split out parsing strictness into four different options:
 * TRAILING     - check that there's no trailing data after parsing
                  attributes (in message or nested)
 * MAXTYPE      - reject attrs > max known type
 * UNSPEC       - reject attributes with NLA_UNSPEC policy entries
 * STRICT_ATTRS - strictly validate attribute size

The default for future things should be *everything*.
The current *_strict() is a combination of TRAILING and MAXTYPE,
and is renamed to _deprecated_strict().
The current regular parsing has none of this, and is renamed to
*_parse_deprecated().

Additionally it allows us to selectively set one of the new flags
even on old policies. Notably, the UNSPEC flag could be useful in
this case, since it can be arranged (by filling in the policy) to
not be an incompatible userspace ABI change, but would then going
forward prevent forgetting attribute entries. Similar can apply
to the POLICY flag.

We end up with the following renames:
 * nla_parse           -> nla_parse_deprecated
 * nla_parse_strict    -> nla_parse_deprecated_strict
 * nlmsg_parse         -> nlmsg_parse_deprecated
 * nlmsg_parse_strict  -> nlmsg_parse_deprecated_strict
 * nla_parse_nested    -> nla_parse_nested_deprecated
 * nla_validate_nested -> nla_validate_nested_deprecated

Using spatch, of course:
    @@
    expression TB, MAX, HEAD, LEN, POL, EXT;
    @@
    -nla_parse(TB, MAX, HEAD, LEN, POL, EXT)
    +nla_parse_deprecated(TB, MAX, HEAD, LEN, POL, EXT)

    @@
    expression NLH, HDRLEN, TB, MAX, POL, EXT;
    @@
    -nlmsg_parse(NLH, HDRLEN, TB, MAX, POL, EXT)
    +nlmsg_parse_deprecated(NLH, HDRLEN, TB, MAX, POL, EXT)

    @@
    expression NLH, HDRLEN, TB, MAX, POL, EXT;
    @@
    -nlmsg_parse_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
    +nlmsg_parse_deprecated_strict(NLH, HDRLEN, TB, MAX, POL, EXT)

    @@
    expression TB, MAX, NLA, POL, EXT;
    @@
    -nla_parse_nested(TB, MAX, NLA, POL, EXT)
    +nla_parse_nested_deprecated(TB, MAX, NLA, POL, EXT)

    @@
    expression START, MAX, POL, EXT;
    @@
    -nla_validate_nested(START, MAX, POL, EXT)
    +nla_validate_nested_deprecated(START, MAX, POL, EXT)

    @@
    expression NLH, HDRLEN, MAX, POL, EXT;
    @@
    -nlmsg_validate(NLH, HDRLEN, MAX, POL, EXT)
    +nlmsg_validate_deprecated(NLH, HDRLEN, MAX, POL, EXT)

For this patch, don't actually add the strict, non-renamed versions
yet so that it breaks compile if I get it wrong.

Also, while at it, make nla_validate and nla_parse go down to a
common __nla_validate_parse() function to avoid code duplication.

Ultimately, this allows us to have very strict validation for every
new caller of nla_parse()/nlmsg_parse() etc as re-introduced in the
next patch, while existing things will continue to work as is.

In effect then, this adds fully strict validation for any new command.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-27 17:07:21 -04:00

800 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system. DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* DECnet Routing Forwarding Information Base (Glue/Info List)
*
* Author: Steve Whitehouse <SteveW@ACM.org>
*
*
* Changes:
* Alexey Kuznetsov : SMP locking changes
* Steve Whitehouse : Rewrote it... Well to be more correct, I
* copied most of it from the ipv4 fib code.
* Steve Whitehouse : Updated it in style and fixed a few bugs
* which were fixed in the ipv4 code since
* this code was copied from it.
*
*/
#include <linux/string.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/slab.h>
#include <linux/sockios.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/proc_fs.h>
#include <linux/netdevice.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <linux/uaccess.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/flow.h>
#include <net/fib_rules.h>
#include <net/dn.h>
#include <net/dn_route.h>
#include <net/dn_fib.h>
#include <net/dn_neigh.h>
#include <net/dn_dev.h>
#include <net/rtnh.h>
#define RT_MIN_TABLE 1
#define for_fib_info() { struct dn_fib_info *fi;\
for(fi = dn_fib_info_list; fi; fi = fi->fib_next)
#define endfor_fib_info() }
#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\
for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)
#define change_nexthops(fi) { int nhsel; struct dn_fib_nh *nh;\
for(nhsel = 0, nh = (struct dn_fib_nh *)((fi)->fib_nh); nhsel < (fi)->fib_nhs; nh++, nhsel++)
#define endfor_nexthops(fi) }
static DEFINE_SPINLOCK(dn_fib_multipath_lock);
static struct dn_fib_info *dn_fib_info_list;
static DEFINE_SPINLOCK(dn_fib_info_lock);
static struct
{
int error;
u8 scope;
} dn_fib_props[RTN_MAX+1] = {
[RTN_UNSPEC] = { .error = 0, .scope = RT_SCOPE_NOWHERE },
[RTN_UNICAST] = { .error = 0, .scope = RT_SCOPE_UNIVERSE },
[RTN_LOCAL] = { .error = 0, .scope = RT_SCOPE_HOST },
[RTN_BROADCAST] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
[RTN_ANYCAST] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
[RTN_MULTICAST] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
[RTN_BLACKHOLE] = { .error = -EINVAL, .scope = RT_SCOPE_UNIVERSE },
[RTN_UNREACHABLE] = { .error = -EHOSTUNREACH, .scope = RT_SCOPE_UNIVERSE },
[RTN_PROHIBIT] = { .error = -EACCES, .scope = RT_SCOPE_UNIVERSE },
[RTN_THROW] = { .error = -EAGAIN, .scope = RT_SCOPE_UNIVERSE },
[RTN_NAT] = { .error = 0, .scope = RT_SCOPE_NOWHERE },
[RTN_XRESOLVE] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
};
static int dn_fib_sync_down(__le16 local, struct net_device *dev, int force);
static int dn_fib_sync_up(struct net_device *dev);
void dn_fib_free_info(struct dn_fib_info *fi)
{
if (fi->fib_dead == 0) {
printk(KERN_DEBUG "DECnet: BUG! Attempt to free alive dn_fib_info\n");
return;
}
change_nexthops(fi) {
if (nh->nh_dev)
dev_put(nh->nh_dev);
nh->nh_dev = NULL;
} endfor_nexthops(fi);
kfree(fi);
}
void dn_fib_release_info(struct dn_fib_info *fi)
{
spin_lock(&dn_fib_info_lock);
if (fi && --fi->fib_treeref == 0) {
if (fi->fib_next)
fi->fib_next->fib_prev = fi->fib_prev;
if (fi->fib_prev)
fi->fib_prev->fib_next = fi->fib_next;
if (fi == dn_fib_info_list)
dn_fib_info_list = fi->fib_next;
fi->fib_dead = 1;
dn_fib_info_put(fi);
}
spin_unlock(&dn_fib_info_lock);
}
static inline int dn_fib_nh_comp(const struct dn_fib_info *fi, const struct dn_fib_info *ofi)
{
const struct dn_fib_nh *onh = ofi->fib_nh;
for_nexthops(fi) {
if (nh->nh_oif != onh->nh_oif ||
nh->nh_gw != onh->nh_gw ||
nh->nh_scope != onh->nh_scope ||
nh->nh_weight != onh->nh_weight ||
((nh->nh_flags^onh->nh_flags)&~RTNH_F_DEAD))
return -1;
onh++;
} endfor_nexthops(fi);
return 0;
}
static inline struct dn_fib_info *dn_fib_find_info(const struct dn_fib_info *nfi)
{
for_fib_info() {
if (fi->fib_nhs != nfi->fib_nhs)
continue;
if (nfi->fib_protocol == fi->fib_protocol &&
nfi->fib_prefsrc == fi->fib_prefsrc &&
nfi->fib_priority == fi->fib_priority &&
memcmp(nfi->fib_metrics, fi->fib_metrics, sizeof(fi->fib_metrics)) == 0 &&
((nfi->fib_flags^fi->fib_flags)&~RTNH_F_DEAD) == 0 &&
(nfi->fib_nhs == 0 || dn_fib_nh_comp(fi, nfi) == 0))
return fi;
} endfor_fib_info();
return NULL;
}
static int dn_fib_count_nhs(const struct nlattr *attr)
{
struct rtnexthop *nhp = nla_data(attr);
int nhs = 0, nhlen = nla_len(attr);
while (rtnh_ok(nhp, nhlen)) {
nhs++;
nhp = rtnh_next(nhp, &nhlen);
}
/* leftover implies invalid nexthop configuration, discard it */
return nhlen > 0 ? 0 : nhs;
}
static int dn_fib_get_nhs(struct dn_fib_info *fi, const struct nlattr *attr,
const struct rtmsg *r)
{
struct rtnexthop *nhp = nla_data(attr);
int nhlen = nla_len(attr);
change_nexthops(fi) {
int attrlen;
if (!rtnh_ok(nhp, nhlen))
return -EINVAL;
nh->nh_flags = (r->rtm_flags&~0xFF) | nhp->rtnh_flags;
nh->nh_oif = nhp->rtnh_ifindex;
nh->nh_weight = nhp->rtnh_hops + 1;
attrlen = rtnh_attrlen(nhp);
if (attrlen > 0) {
struct nlattr *gw_attr;
gw_attr = nla_find((struct nlattr *) (nhp + 1), attrlen, RTA_GATEWAY);
nh->nh_gw = gw_attr ? nla_get_le16(gw_attr) : 0;
}
nhp = rtnh_next(nhp, &nhlen);
} endfor_nexthops(fi);
return 0;
}
static int dn_fib_check_nh(const struct rtmsg *r, struct dn_fib_info *fi, struct dn_fib_nh *nh)
{
int err;
if (nh->nh_gw) {
struct flowidn fld;
struct dn_fib_res res;
if (nh->nh_flags&RTNH_F_ONLINK) {
struct net_device *dev;
if (r->rtm_scope >= RT_SCOPE_LINK)
return -EINVAL;
if (dnet_addr_type(nh->nh_gw) != RTN_UNICAST)
return -EINVAL;
if ((dev = __dev_get_by_index(&init_net, nh->nh_oif)) == NULL)
return -ENODEV;
if (!(dev->flags&IFF_UP))
return -ENETDOWN;
nh->nh_dev = dev;
dev_hold(dev);
nh->nh_scope = RT_SCOPE_LINK;
return 0;
}
memset(&fld, 0, sizeof(fld));
fld.daddr = nh->nh_gw;
fld.flowidn_oif = nh->nh_oif;
fld.flowidn_scope = r->rtm_scope + 1;
if (fld.flowidn_scope < RT_SCOPE_LINK)
fld.flowidn_scope = RT_SCOPE_LINK;
if ((err = dn_fib_lookup(&fld, &res)) != 0)
return err;
err = -EINVAL;
if (res.type != RTN_UNICAST && res.type != RTN_LOCAL)
goto out;
nh->nh_scope = res.scope;
nh->nh_oif = DN_FIB_RES_OIF(res);
nh->nh_dev = DN_FIB_RES_DEV(res);
if (nh->nh_dev == NULL)
goto out;
dev_hold(nh->nh_dev);
err = -ENETDOWN;
if (!(nh->nh_dev->flags & IFF_UP))
goto out;
err = 0;
out:
dn_fib_res_put(&res);
return err;
} else {
struct net_device *dev;
if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK))
return -EINVAL;
dev = __dev_get_by_index(&init_net, nh->nh_oif);
if (dev == NULL || dev->dn_ptr == NULL)
return -ENODEV;
if (!(dev->flags&IFF_UP))
return -ENETDOWN;
nh->nh_dev = dev;
dev_hold(nh->nh_dev);
nh->nh_scope = RT_SCOPE_HOST;
}
return 0;
}
struct dn_fib_info *dn_fib_create_info(const struct rtmsg *r, struct nlattr *attrs[],
const struct nlmsghdr *nlh, int *errp)
{
int err;
struct dn_fib_info *fi = NULL;
struct dn_fib_info *ofi;
int nhs = 1;
if (r->rtm_type > RTN_MAX)
goto err_inval;
if (dn_fib_props[r->rtm_type].scope > r->rtm_scope)
goto err_inval;
if (attrs[RTA_MULTIPATH] &&
(nhs = dn_fib_count_nhs(attrs[RTA_MULTIPATH])) == 0)
goto err_inval;
fi = kzalloc(struct_size(fi, fib_nh, nhs), GFP_KERNEL);
err = -ENOBUFS;
if (fi == NULL)
goto failure;
fi->fib_protocol = r->rtm_protocol;
fi->fib_nhs = nhs;
fi->fib_flags = r->rtm_flags;
if (attrs[RTA_PRIORITY])
fi->fib_priority = nla_get_u32(attrs[RTA_PRIORITY]);
if (attrs[RTA_METRICS]) {
struct nlattr *attr;
int rem;
nla_for_each_nested(attr, attrs[RTA_METRICS], rem) {
int type = nla_type(attr);
if (type) {
if (type > RTAX_MAX || type == RTAX_CC_ALGO ||
nla_len(attr) < 4)
goto err_inval;
fi->fib_metrics[type-1] = nla_get_u32(attr);
}
}
}
if (attrs[RTA_PREFSRC])
fi->fib_prefsrc = nla_get_le16(attrs[RTA_PREFSRC]);
if (attrs[RTA_MULTIPATH]) {
if ((err = dn_fib_get_nhs(fi, attrs[RTA_MULTIPATH], r)) != 0)
goto failure;
if (attrs[RTA_OIF] &&
fi->fib_nh->nh_oif != nla_get_u32(attrs[RTA_OIF]))
goto err_inval;
if (attrs[RTA_GATEWAY] &&
fi->fib_nh->nh_gw != nla_get_le16(attrs[RTA_GATEWAY]))
goto err_inval;
} else {
struct dn_fib_nh *nh = fi->fib_nh;
if (attrs[RTA_OIF])
nh->nh_oif = nla_get_u32(attrs[RTA_OIF]);
if (attrs[RTA_GATEWAY])
nh->nh_gw = nla_get_le16(attrs[RTA_GATEWAY]);
nh->nh_flags = r->rtm_flags;
nh->nh_weight = 1;
}
if (r->rtm_type == RTN_NAT) {
if (!attrs[RTA_GATEWAY] || nhs != 1 || attrs[RTA_OIF])
goto err_inval;
fi->fib_nh->nh_gw = nla_get_le16(attrs[RTA_GATEWAY]);
goto link_it;
}
if (dn_fib_props[r->rtm_type].error) {
if (attrs[RTA_GATEWAY] || attrs[RTA_OIF] || attrs[RTA_MULTIPATH])
goto err_inval;
goto link_it;
}
if (r->rtm_scope > RT_SCOPE_HOST)
goto err_inval;
if (r->rtm_scope == RT_SCOPE_HOST) {
struct dn_fib_nh *nh = fi->fib_nh;
/* Local address is added */
if (nhs != 1 || nh->nh_gw)
goto err_inval;
nh->nh_scope = RT_SCOPE_NOWHERE;
nh->nh_dev = dev_get_by_index(&init_net, fi->fib_nh->nh_oif);
err = -ENODEV;
if (nh->nh_dev == NULL)
goto failure;
} else {
change_nexthops(fi) {
if ((err = dn_fib_check_nh(r, fi, nh)) != 0)
goto failure;
} endfor_nexthops(fi)
}
if (fi->fib_prefsrc) {
if (r->rtm_type != RTN_LOCAL || !attrs[RTA_DST] ||
fi->fib_prefsrc != nla_get_le16(attrs[RTA_DST]))
if (dnet_addr_type(fi->fib_prefsrc) != RTN_LOCAL)
goto err_inval;
}
link_it:
if ((ofi = dn_fib_find_info(fi)) != NULL) {
fi->fib_dead = 1;
dn_fib_free_info(fi);
ofi->fib_treeref++;
return ofi;
}
fi->fib_treeref++;
refcount_set(&fi->fib_clntref, 1);
spin_lock(&dn_fib_info_lock);
fi->fib_next = dn_fib_info_list;
fi->fib_prev = NULL;
if (dn_fib_info_list)
dn_fib_info_list->fib_prev = fi;
dn_fib_info_list = fi;
spin_unlock(&dn_fib_info_lock);
return fi;
err_inval:
err = -EINVAL;
failure:
*errp = err;
if (fi) {
fi->fib_dead = 1;
dn_fib_free_info(fi);
}
return NULL;
}
int dn_fib_semantic_match(int type, struct dn_fib_info *fi, const struct flowidn *fld, struct dn_fib_res *res)
{
int err = dn_fib_props[type].error;
if (err == 0) {
if (fi->fib_flags & RTNH_F_DEAD)
return 1;
res->fi = fi;
switch (type) {
case RTN_NAT:
DN_FIB_RES_RESET(*res);
refcount_inc(&fi->fib_clntref);
return 0;
case RTN_UNICAST:
case RTN_LOCAL:
for_nexthops(fi) {
if (nh->nh_flags & RTNH_F_DEAD)
continue;
if (!fld->flowidn_oif ||
fld->flowidn_oif == nh->nh_oif)
break;
}
if (nhsel < fi->fib_nhs) {
res->nh_sel = nhsel;
refcount_inc(&fi->fib_clntref);
return 0;
}
endfor_nexthops(fi);
res->fi = NULL;
return 1;
default:
net_err_ratelimited("DECnet: impossible routing event : dn_fib_semantic_match type=%d\n",
type);
res->fi = NULL;
return -EINVAL;
}
}
return err;
}
void dn_fib_select_multipath(const struct flowidn *fld, struct dn_fib_res *res)
{
struct dn_fib_info *fi = res->fi;
int w;
spin_lock_bh(&dn_fib_multipath_lock);
if (fi->fib_power <= 0) {
int power = 0;
change_nexthops(fi) {
if (!(nh->nh_flags&RTNH_F_DEAD)) {
power += nh->nh_weight;
nh->nh_power = nh->nh_weight;
}
} endfor_nexthops(fi);
fi->fib_power = power;
if (power < 0) {
spin_unlock_bh(&dn_fib_multipath_lock);
res->nh_sel = 0;
return;
}
}
w = jiffies % fi->fib_power;
change_nexthops(fi) {
if (!(nh->nh_flags&RTNH_F_DEAD) && nh->nh_power) {
if ((w -= nh->nh_power) <= 0) {
nh->nh_power--;
fi->fib_power--;
res->nh_sel = nhsel;
spin_unlock_bh(&dn_fib_multipath_lock);
return;
}
}
} endfor_nexthops(fi);
res->nh_sel = 0;
spin_unlock_bh(&dn_fib_multipath_lock);
}
static inline u32 rtm_get_table(struct nlattr *attrs[], u8 table)
{
if (attrs[RTA_TABLE])
table = nla_get_u32(attrs[RTA_TABLE]);
return table;
}
static int dn_fib_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
struct net *net = sock_net(skb->sk);
struct dn_fib_table *tb;
struct rtmsg *r = nlmsg_data(nlh);
struct nlattr *attrs[RTA_MAX+1];
int err;
if (!netlink_capable(skb, CAP_NET_ADMIN))
return -EPERM;
if (!net_eq(net, &init_net))
return -EINVAL;
err = nlmsg_parse_deprecated(nlh, sizeof(*r), attrs, RTA_MAX,
rtm_dn_policy, extack);
if (err < 0)
return err;
tb = dn_fib_get_table(rtm_get_table(attrs, r->rtm_table), 0);
if (!tb)
return -ESRCH;
return tb->delete(tb, r, attrs, nlh, &NETLINK_CB(skb));
}
static int dn_fib_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
struct net *net = sock_net(skb->sk);
struct dn_fib_table *tb;
struct rtmsg *r = nlmsg_data(nlh);
struct nlattr *attrs[RTA_MAX+1];
int err;
if (!netlink_capable(skb, CAP_NET_ADMIN))
return -EPERM;
if (!net_eq(net, &init_net))
return -EINVAL;
err = nlmsg_parse_deprecated(nlh, sizeof(*r), attrs, RTA_MAX,
rtm_dn_policy, extack);
if (err < 0)
return err;
tb = dn_fib_get_table(rtm_get_table(attrs, r->rtm_table), 1);
if (!tb)
return -ENOBUFS;
return tb->insert(tb, r, attrs, nlh, &NETLINK_CB(skb));
}
static void fib_magic(int cmd, int type, __le16 dst, int dst_len, struct dn_ifaddr *ifa)
{
struct dn_fib_table *tb;
struct {
struct nlmsghdr nlh;
struct rtmsg rtm;
} req;
struct {
struct nlattr hdr;
__le16 dst;
} dst_attr = {
.dst = dst,
};
struct {
struct nlattr hdr;
__le16 prefsrc;
} prefsrc_attr = {
.prefsrc = ifa->ifa_local,
};
struct {
struct nlattr hdr;
u32 oif;
} oif_attr = {
.oif = ifa->ifa_dev->dev->ifindex,
};
struct nlattr *attrs[RTA_MAX+1] = {
[RTA_DST] = (struct nlattr *) &dst_attr,
[RTA_PREFSRC] = (struct nlattr * ) &prefsrc_attr,
[RTA_OIF] = (struct nlattr *) &oif_attr,
};
memset(&req.rtm, 0, sizeof(req.rtm));
if (type == RTN_UNICAST)
tb = dn_fib_get_table(RT_MIN_TABLE, 1);
else
tb = dn_fib_get_table(RT_TABLE_LOCAL, 1);
if (tb == NULL)
return;
req.nlh.nlmsg_len = sizeof(req);
req.nlh.nlmsg_type = cmd;
req.nlh.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_APPEND;
req.nlh.nlmsg_pid = 0;
req.nlh.nlmsg_seq = 0;
req.rtm.rtm_dst_len = dst_len;
req.rtm.rtm_table = tb->n;
req.rtm.rtm_protocol = RTPROT_KERNEL;
req.rtm.rtm_scope = (type != RTN_LOCAL ? RT_SCOPE_LINK : RT_SCOPE_HOST);
req.rtm.rtm_type = type;
if (cmd == RTM_NEWROUTE)
tb->insert(tb, &req.rtm, attrs, &req.nlh, NULL);
else
tb->delete(tb, &req.rtm, attrs, &req.nlh, NULL);
}
static void dn_fib_add_ifaddr(struct dn_ifaddr *ifa)
{
fib_magic(RTM_NEWROUTE, RTN_LOCAL, ifa->ifa_local, 16, ifa);
#if 0
if (!(dev->flags&IFF_UP))
return;
/* In the future, we will want to add default routes here */
#endif
}
static void dn_fib_del_ifaddr(struct dn_ifaddr *ifa)
{
int found_it = 0;
struct net_device *dev;
struct dn_dev *dn_db;
struct dn_ifaddr *ifa2;
ASSERT_RTNL();
/* Scan device list */
rcu_read_lock();
for_each_netdev_rcu(&init_net, dev) {
dn_db = rcu_dereference(dev->dn_ptr);
if (dn_db == NULL)
continue;
for (ifa2 = rcu_dereference(dn_db->ifa_list);
ifa2 != NULL;
ifa2 = rcu_dereference(ifa2->ifa_next)) {
if (ifa2->ifa_local == ifa->ifa_local) {
found_it = 1;
break;
}
}
}
rcu_read_unlock();
if (found_it == 0) {
fib_magic(RTM_DELROUTE, RTN_LOCAL, ifa->ifa_local, 16, ifa);
if (dnet_addr_type(ifa->ifa_local) != RTN_LOCAL) {
if (dn_fib_sync_down(ifa->ifa_local, NULL, 0))
dn_fib_flush();
}
}
}
static void dn_fib_disable_addr(struct net_device *dev, int force)
{
if (dn_fib_sync_down(0, dev, force))
dn_fib_flush();
dn_rt_cache_flush(0);
neigh_ifdown(&dn_neigh_table, dev);
}
static int dn_fib_dnaddr_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct dn_ifaddr *ifa = (struct dn_ifaddr *)ptr;
switch (event) {
case NETDEV_UP:
dn_fib_add_ifaddr(ifa);
dn_fib_sync_up(ifa->ifa_dev->dev);
dn_rt_cache_flush(-1);
break;
case NETDEV_DOWN:
dn_fib_del_ifaddr(ifa);
if (ifa->ifa_dev && ifa->ifa_dev->ifa_list == NULL) {
dn_fib_disable_addr(ifa->ifa_dev->dev, 1);
} else {
dn_rt_cache_flush(-1);
}
break;
}
return NOTIFY_DONE;
}
static int dn_fib_sync_down(__le16 local, struct net_device *dev, int force)
{
int ret = 0;
int scope = RT_SCOPE_NOWHERE;
if (force)
scope = -1;
for_fib_info() {
/*
* This makes no sense for DECnet.... we will almost
* certainly have more than one local address the same
* over all our interfaces. It needs thinking about
* some more.
*/
if (local && fi->fib_prefsrc == local) {
fi->fib_flags |= RTNH_F_DEAD;
ret++;
} else if (dev && fi->fib_nhs) {
int dead = 0;
change_nexthops(fi) {
if (nh->nh_flags&RTNH_F_DEAD)
dead++;
else if (nh->nh_dev == dev &&
nh->nh_scope != scope) {
spin_lock_bh(&dn_fib_multipath_lock);
nh->nh_flags |= RTNH_F_DEAD;
fi->fib_power -= nh->nh_power;
nh->nh_power = 0;
spin_unlock_bh(&dn_fib_multipath_lock);
dead++;
}
} endfor_nexthops(fi)
if (dead == fi->fib_nhs) {
fi->fib_flags |= RTNH_F_DEAD;
ret++;
}
}
} endfor_fib_info();
return ret;
}
static int dn_fib_sync_up(struct net_device *dev)
{
int ret = 0;
if (!(dev->flags&IFF_UP))
return 0;
for_fib_info() {
int alive = 0;
change_nexthops(fi) {
if (!(nh->nh_flags&RTNH_F_DEAD)) {
alive++;
continue;
}
if (nh->nh_dev == NULL || !(nh->nh_dev->flags&IFF_UP))
continue;
if (nh->nh_dev != dev || dev->dn_ptr == NULL)
continue;
alive++;
spin_lock_bh(&dn_fib_multipath_lock);
nh->nh_power = 0;
nh->nh_flags &= ~RTNH_F_DEAD;
spin_unlock_bh(&dn_fib_multipath_lock);
} endfor_nexthops(fi);
if (alive > 0) {
fi->fib_flags &= ~RTNH_F_DEAD;
ret++;
}
} endfor_fib_info();
return ret;
}
static struct notifier_block dn_fib_dnaddr_notifier = {
.notifier_call = dn_fib_dnaddr_event,
};
void __exit dn_fib_cleanup(void)
{
dn_fib_table_cleanup();
dn_fib_rules_cleanup();
unregister_dnaddr_notifier(&dn_fib_dnaddr_notifier);
}
void __init dn_fib_init(void)
{
dn_fib_table_init();
dn_fib_rules_init();
register_dnaddr_notifier(&dn_fib_dnaddr_notifier);
rtnl_register_module(THIS_MODULE, PF_DECnet, RTM_NEWROUTE,
dn_fib_rtm_newroute, NULL, 0);
rtnl_register_module(THIS_MODULE, PF_DECnet, RTM_DELROUTE,
dn_fib_rtm_delroute, NULL, 0);
}