Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 83c44251 authored by David Ahern's avatar David Ahern Committed by David S. Miller
Browse files

ipv6: Create init helper for fib6_nh



Similar to IPv4, consolidate the fib6_nh initialization into a helper.
As a new standalone function, add a cleanup path to put lwtstate on
error.

To avoid modifying fib6_config flags, move the reject check to a helper
that is invoked once by fib6_nh_init to reset the device and then
again in ip6_route_info_create to set the fib6_flags.

Signed-off-by: default avatarDavid Ahern <dsahern@gmail.com>
Reviewed-by: default avatarIdo Schimmel <idosch@mellanox.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent faa041a4
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -444,6 +444,10 @@ static inline struct net_device *fib6_info_nh_dev(const struct fib6_info *f6i)
	return f6i->fib6_nh.nh_dev;
}

int fib6_nh_init(struct net *net, struct fib6_nh *fib6_nh,
		 struct fib6_config *cfg, gfp_t gfp_flags,
		 struct netlink_ext_ack *extack);

static inline
struct lwtunnel_state *fib6_info_nh_lwt(const struct fib6_info *f6i)
{
+141 −108
Original line number Diff line number Diff line
@@ -2898,17 +2898,142 @@ static int ip6_validate_gw(struct net *net, struct fib6_config *cfg,
	return err;
}

static bool fib6_is_reject(u32 flags, struct net_device *dev, int addr_type)
{
	if ((flags & RTF_REJECT) ||
	    (dev && (dev->flags & IFF_LOOPBACK) &&
	     !(addr_type & IPV6_ADDR_LOOPBACK) &&
	     !(flags & RTF_LOCAL)))
		return true;

	return false;
}

int fib6_nh_init(struct net *net, struct fib6_nh *fib6_nh,
		 struct fib6_config *cfg, gfp_t gfp_flags,
		 struct netlink_ext_ack *extack)
{
	struct net_device *dev = NULL;
	struct inet6_dev *idev = NULL;
	int addr_type;
	int err;

	err = -ENODEV;
	if (cfg->fc_ifindex) {
		dev = dev_get_by_index(net, cfg->fc_ifindex);
		if (!dev)
			goto out;
		idev = in6_dev_get(dev);
		if (!idev)
			goto out;
	}

	if (cfg->fc_flags & RTNH_F_ONLINK) {
		if (!dev) {
			NL_SET_ERR_MSG(extack,
				       "Nexthop device required for onlink");
			goto out;
		}

		if (!(dev->flags & IFF_UP)) {
			NL_SET_ERR_MSG(extack, "Nexthop device is not up");
			err = -ENETDOWN;
			goto out;
		}

		fib6_nh->nh_flags |= RTNH_F_ONLINK;
	}

	if (cfg->fc_encap) {
		struct lwtunnel_state *lwtstate;

		err = lwtunnel_build_state(cfg->fc_encap_type,
					   cfg->fc_encap, AF_INET6, cfg,
					   &lwtstate, extack);
		if (err)
			goto out;

		fib6_nh->nh_lwtstate = lwtstate_get(lwtstate);
	}

	fib6_nh->nh_weight = 1;

	/* We cannot add true routes via loopback here,
	 * they would result in kernel looping; promote them to reject routes
	 */
	addr_type = ipv6_addr_type(&cfg->fc_dst);
	if (fib6_is_reject(cfg->fc_flags, dev, addr_type)) {
		/* hold loopback dev/idev if we haven't done so. */
		if (dev != net->loopback_dev) {
			if (dev) {
				dev_put(dev);
				in6_dev_put(idev);
			}
			dev = net->loopback_dev;
			dev_hold(dev);
			idev = in6_dev_get(dev);
			if (!idev) {
				err = -ENODEV;
				goto out;
			}
		}
		goto set_dev;
	}

	if (cfg->fc_flags & RTF_GATEWAY) {
		err = ip6_validate_gw(net, cfg, &dev, &idev, extack);
		if (err)
			goto out;

		fib6_nh->nh_gw = cfg->fc_gateway;
	}

	err = -ENODEV;
	if (!dev)
		goto out;

	if (idev->cnf.disable_ipv6) {
		NL_SET_ERR_MSG(extack, "IPv6 is disabled on nexthop device");
		err = -EACCES;
		goto out;
	}

	if (!(dev->flags & IFF_UP) && !cfg->fc_ignore_dev_down) {
		NL_SET_ERR_MSG(extack, "Nexthop device is not up");
		err = -ENETDOWN;
		goto out;
	}

	if (!(cfg->fc_flags & (RTF_LOCAL | RTF_ANYCAST)) &&
	    !netif_carrier_ok(dev))
		fib6_nh->nh_flags |= RTNH_F_LINKDOWN;

set_dev:
	fib6_nh->nh_dev = dev;
	err = 0;
out:
	if (idev)
		in6_dev_put(idev);

	if (err) {
		lwtstate_put(fib6_nh->nh_lwtstate);
		fib6_nh->nh_lwtstate = NULL;
		if (dev)
			dev_put(dev);
	}

	return err;
}

static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
					      gfp_t gfp_flags,
					      struct netlink_ext_ack *extack)
{
	struct net *net = cfg->fc_nlinfo.nl_net;
	struct fib6_info *rt = NULL;
	struct net_device *dev = NULL;
	struct inet6_dev *idev = NULL;
	struct fib6_table *table;
	int addr_type;
	int err = -EINVAL;
	int addr_type;

	/* RTF_PCPU is an internal flag; can not be set by userspace */
	if (cfg->fc_flags & RTF_PCPU) {
@@ -2942,30 +3067,6 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
		goto out;
	}
#endif
	if (cfg->fc_ifindex) {
		err = -ENODEV;
		dev = dev_get_by_index(net, cfg->fc_ifindex);
		if (!dev)
			goto out;
		idev = in6_dev_get(dev);
		if (!idev)
			goto out;
	}

	if (cfg->fc_flags & RTNH_F_ONLINK) {
		if (!dev) {
			NL_SET_ERR_MSG(extack,
				       "Nexthop device required for onlink");
			err = -ENODEV;
			goto out;
		}

		if (!(dev->flags & IFF_UP)) {
			NL_SET_ERR_MSG(extack, "Nexthop device is not up");
			err = -ENETDOWN;
			goto out;
		}
	}

	err = -ENOBUFS;
	if (cfg->fc_nlinfo.nlh &&
@@ -3009,18 +3110,10 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
		cfg->fc_protocol = RTPROT_BOOT;
	rt->fib6_protocol = cfg->fc_protocol;

	addr_type = ipv6_addr_type(&cfg->fc_dst);

	if (cfg->fc_encap) {
		struct lwtunnel_state *lwtstate;

		err = lwtunnel_build_state(cfg->fc_encap_type,
					   cfg->fc_encap, AF_INET6, cfg,
					   &lwtstate, extack);
		if (err)
			goto out;
		rt->fib6_nh.nh_lwtstate = lwtstate_get(lwtstate);
	}
	rt->fib6_table = table;
	rt->fib6_metric = cfg->fc_metric;
	rt->fib6_type = cfg->fc_type;
	rt->fib6_flags = cfg->fc_flags;

	ipv6_addr_prefix(&rt->fib6_dst.addr, &cfg->fc_dst, cfg->fc_dst_len);
	rt->fib6_dst.plen = cfg->fc_dst_len;
@@ -3031,62 +3124,20 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
	ipv6_addr_prefix(&rt->fib6_src.addr, &cfg->fc_src, cfg->fc_src_len);
	rt->fib6_src.plen = cfg->fc_src_len;
#endif

	rt->fib6_metric = cfg->fc_metric;
	rt->fib6_nh.nh_weight = 1;

	rt->fib6_type = cfg->fc_type;
	err = fib6_nh_init(net, &rt->fib6_nh, cfg, gfp_flags, extack);
	if (err)
		goto out;

	/* We cannot add true routes via loopback here,
	   they would result in kernel looping; promote them to reject routes
	 * they would result in kernel looping; promote them to reject routes
	 */
	if ((cfg->fc_flags & RTF_REJECT) ||
	    (dev && (dev->flags & IFF_LOOPBACK) &&
	     !(addr_type & IPV6_ADDR_LOOPBACK) &&
	     !(cfg->fc_flags & RTF_LOCAL))) {
		/* hold loopback dev/idev if we haven't done so. */
		if (dev != net->loopback_dev) {
			if (dev) {
				dev_put(dev);
				in6_dev_put(idev);
			}
			dev = net->loopback_dev;
			dev_hold(dev);
			idev = in6_dev_get(dev);
			if (!idev) {
				err = -ENODEV;
				goto out;
			}
		}
	addr_type = ipv6_addr_type(&cfg->fc_dst);
	if (fib6_is_reject(cfg->fc_flags, rt->fib6_nh.nh_dev, addr_type))
		rt->fib6_flags = RTF_REJECT | RTF_NONEXTHOP;
		goto install_route;
	}

	if (cfg->fc_flags & RTF_GATEWAY) {
		err = ip6_validate_gw(net, cfg, &dev, &idev, extack);
		if (err)
			goto out;

		rt->fib6_nh.nh_gw = cfg->fc_gateway;
	}

	err = -ENODEV;
	if (!dev)
		goto out;

	if (idev->cnf.disable_ipv6) {
		NL_SET_ERR_MSG(extack, "IPv6 is disabled on nexthop device");
		err = -EACCES;
		goto out;
	}

	if (!(dev->flags & IFF_UP) && !cfg->fc_ignore_dev_down) {
		NL_SET_ERR_MSG(extack, "Nexthop device is not up");
		err = -ENETDOWN;
		goto out;
	}

	if (!ipv6_addr_any(&cfg->fc_prefsrc)) {
		struct net_device *dev = fib6_info_nh_dev(rt);

		if (!ipv6_chk_addr(net, &cfg->fc_prefsrc, dev, 0)) {
			NL_SET_ERR_MSG(extack, "Invalid source address");
			err = -EINVAL;
@@ -3097,26 +3148,8 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
	} else
		rt->fib6_prefsrc.plen = 0;

	rt->fib6_flags = cfg->fc_flags;

install_route:
	if (!(rt->fib6_flags & (RTF_LOCAL | RTF_ANYCAST)) &&
	    !netif_carrier_ok(dev))
		rt->fib6_nh.nh_flags |= RTNH_F_LINKDOWN;
	rt->fib6_nh.nh_flags |= (cfg->fc_flags & RTNH_F_ONLINK);
	rt->fib6_nh.nh_dev = dev;
	rt->fib6_table = table;

	if (idev)
		in6_dev_put(idev);

	return rt;
out:
	if (dev)
		dev_put(dev);
	if (idev)
		in6_dev_put(idev);

	fib6_info_release(rt);
	return ERR_PTR(err);
}