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

Commit c3968a85 authored by Daniel Walter's avatar Daniel Walter Committed by David S. Miller
Browse files

ipv6: RTA_PREFSRC support for ipv6 route source address selection



[ipv6] Add support for RTA_PREFSRC

This patch allows a user to select the preferred source address
for a specific IPv6-Route. It can be set via a netlink message
setting RTA_PREFSRC to a valid IPv6 address which must be
up on the device the route will be bound to.

Signed-off-by: default avatarDaniel Walter <dwalter@barracuda.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent bd015928
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ struct fib6_config {

	struct in6_addr	fc_dst;
	struct in6_addr	fc_src;
	struct in6_addr	fc_prefsrc;
	struct in6_addr	fc_gateway;

	unsigned long	fc_expires;
@@ -107,6 +108,7 @@ struct rt6_info {
	struct rt6key			rt6i_dst ____cacheline_aligned_in_smp;
	u32				rt6i_flags;
	struct rt6key			rt6i_src;
	struct rt6key			rt6i_prefsrc;
	u32				rt6i_metric;
	u32				rt6i_peer_genid;

+7 −0
Original line number Diff line number Diff line
@@ -84,6 +84,12 @@ extern int ip6_route_add(struct fib6_config *cfg);
extern int			ip6_ins_rt(struct rt6_info *);
extern int			ip6_del_rt(struct rt6_info *);

extern int			ip6_route_get_saddr(struct net *net,
						    struct rt6_info *rt,
						    struct in6_addr *daddr,
						    unsigned int prefs,
						    struct in6_addr *saddr);

extern struct rt6_info		*rt6_lookup(struct net *net,
					    const struct in6_addr *daddr,
					    const struct in6_addr *saddr,
@@ -141,6 +147,7 @@ struct rt6_rtnl_dump_arg {
extern int rt6_dump_route(struct rt6_info *rt, void *p_arg);
extern void rt6_ifdown(struct net *net, struct net_device *dev);
extern void rt6_mtu_change(struct net_device *dev, unsigned mtu);
extern void rt6_remove_prefsrc(struct inet6_ifaddr *ifp);


/*
+2 −0
Original line number Diff line number Diff line
@@ -825,6 +825,8 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp)
		dst_release(&rt->dst);
	}

	/* clean up prefsrc entries */
	rt6_remove_prefsrc(ifp);
out:
	in6_ifa_put(ifp);
}
+4 −4
Original line number Diff line number Diff line
@@ -930,8 +930,8 @@ static int ip6_dst_lookup_tail(struct sock *sk,
		goto out_err_release;

	if (ipv6_addr_any(&fl6->saddr)) {
		err = ipv6_dev_get_saddr(net, ip6_dst_idev(*dst)->dev,
					 &fl6->daddr,
		struct rt6_info *rt = (struct rt6_info *) *dst;
		err = ip6_route_get_saddr(net, rt, &fl6->daddr,
					  sk ? inet6_sk(sk)->srcprefs : 0,
					  &fl6->saddr);
		if (err)
+69 −3
Original line number Diff line number Diff line
@@ -1325,6 +1325,16 @@ int ip6_route_add(struct fib6_config *cfg)
	if (dev == NULL)
		goto out;

	if (!ipv6_addr_any(&cfg->fc_prefsrc)) {
		if (!ipv6_chk_addr(net, &cfg->fc_prefsrc, dev, 0)) {
			err = -EINVAL;
			goto out;
		}
		ipv6_addr_copy(&rt->rt6i_prefsrc.addr, &cfg->fc_prefsrc);
		rt->rt6i_prefsrc.plen = 128;
	} else
		rt->rt6i_prefsrc.plen = 0;

	if (cfg->fc_flags & (RTF_GATEWAY | RTF_NONEXTHOP)) {
		rt->rt6i_nexthop = __neigh_lookup_errno(&nd_tbl, &rt->rt6i_gateway, dev);
		if (IS_ERR(rt->rt6i_nexthop)) {
@@ -2037,6 +2047,55 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
	return rt;
}

int ip6_route_get_saddr(struct net *net,
			struct rt6_info *rt,
			struct in6_addr *daddr,
			unsigned int prefs,
			struct in6_addr *saddr)
{
	struct inet6_dev *idev = ip6_dst_idev((struct dst_entry*)rt);
	int err = 0;
	if (rt->rt6i_prefsrc.plen)
		ipv6_addr_copy(saddr, &rt->rt6i_prefsrc.addr);
	else
		err = ipv6_dev_get_saddr(net, idev ? idev->dev : NULL,
					 daddr, prefs, saddr);
	return err;
}

/* remove deleted ip from prefsrc entries */
struct arg_dev_net_ip {
	struct net_device *dev;
	struct net *net;
	struct in6_addr *addr;
};

static int fib6_remove_prefsrc(struct rt6_info *rt, void *arg)
{
	struct net_device *dev = ((struct arg_dev_net_ip *)arg)->dev;
	struct net *net = ((struct arg_dev_net_ip *)arg)->net;
	struct in6_addr *addr = ((struct arg_dev_net_ip *)arg)->addr;

	if (((void *)rt->rt6i_dev == dev || dev == NULL) &&
	    rt != net->ipv6.ip6_null_entry &&
	    ipv6_addr_equal(addr, &rt->rt6i_prefsrc.addr)) {
		/* remove prefsrc entry */
		rt->rt6i_prefsrc.plen = 0;
	}
	return 0;
}

void rt6_remove_prefsrc(struct inet6_ifaddr *ifp)
{
	struct net *net = dev_net(ifp->idev->dev);
	struct arg_dev_net_ip adni = {
		.dev = ifp->idev->dev,
		.net = net,
		.addr = &ifp->addr,
	};
	fib6_clean_all(net, fib6_remove_prefsrc, 0, &adni);
}

struct arg_dev_net {
	struct net_device *dev;
	struct net *net;
@@ -2183,6 +2242,9 @@ static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh,
		nla_memcpy(&cfg->fc_src, tb[RTA_SRC], plen);
	}

	if (tb[RTA_PREFSRC])
		nla_memcpy(&cfg->fc_prefsrc, tb[RTA_PREFSRC], 16);

	if (tb[RTA_OIF])
		cfg->fc_ifindex = nla_get_u32(tb[RTA_OIF]);

@@ -2325,10 +2387,14 @@ static int rt6_fill_node(struct net *net,
#endif
			NLA_PUT_U32(skb, RTA_IIF, iif);
	} else if (dst) {
		struct inet6_dev *idev = ip6_dst_idev(&rt->dst);
		struct in6_addr saddr_buf;
		if (ipv6_dev_get_saddr(net, idev ? idev->dev : NULL,
				       dst, 0, &saddr_buf) == 0)
		if (ip6_route_get_saddr(net, rt, dst, 0, &saddr_buf) == 0)
			NLA_PUT(skb, RTA_PREFSRC, 16, &saddr_buf);
	}

	if (rt->rt6i_prefsrc.plen) {
		struct in6_addr saddr_buf;
		ipv6_addr_copy(&saddr_buf, &rt->rt6i_prefsrc.addr);
		NLA_PUT(skb, RTA_PREFSRC, 16, &saddr_buf);
	}