[IPV6]: Support Source Address Selection API (RFC5014).

Signed-off-by: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 787e90a..8995488 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -909,6 +909,7 @@
 	int ifindex;
 	int scope;
 	int label;
+	unsigned int prefs;
 };
 
 static inline int ipv6_saddr_preferred(int type)
@@ -984,9 +985,12 @@
 		break;
 #ifdef CONFIG_IPV6_MIP6
 	case IPV6_SADDR_RULE_HOA:
+	    {
 		/* Rule 4: Prefer home address */
-		ret = !!(score->ifa->flags & IFA_F_HOMEADDRESS);
+		int prefhome = !(dst->prefs & IPV6_PREFER_SRC_COA);
+		ret = !(score->ifa->flags & IFA_F_HOMEADDRESS) ^ prefhome;
 		break;
+	    }
 #endif
 	case IPV6_SADDR_RULE_OIF:
 		/* Rule 5: Prefer outgoing interface */
@@ -1000,11 +1004,16 @@
 		break;
 #ifdef CONFIG_IPV6_PRIVACY
 	case IPV6_SADDR_RULE_PRIVACY:
+	    {
 		/* Rule 7: Prefer public address
 		 * Note: prefer temprary address if use_tempaddr >= 2
 		 */
-		ret = (!(score->ifa->flags & IFA_F_TEMPORARY)) ^ (score->ifa->idev->cnf.use_tempaddr >= 2);
+		int preftmp = dst->prefs & (IPV6_PREFER_SRC_PUBLIC|IPV6_PREFER_SRC_TMP) ?
+				!!(dst->prefs & IPV6_PREFER_SRC_TMP) :
+				score->ifa->idev->cnf.use_tempaddr >= 2;
+		ret = (!(score->ifa->flags & IFA_F_TEMPORARY)) ^ preftmp;
 		break;
+	    }
 #endif
 	case IPV6_SADDR_RULE_ORCHID:
 		/* Rule 8-: Prefer ORCHID vs ORCHID or
@@ -1030,7 +1039,8 @@
 }
 
 int ipv6_dev_get_saddr(struct net_device *dst_dev,
-		       struct in6_addr *daddr, struct in6_addr *saddr)
+		       struct in6_addr *daddr, unsigned int prefs,
+		       struct in6_addr *saddr)
 {
 	struct ipv6_saddr_score scores[2],
 				*score = &scores[0], *hiscore = &scores[1];
@@ -1044,6 +1054,7 @@
 	dst.ifindex = dst_dev ? dst_dev->ifindex : 0;
 	dst.scope = __ipv6_addr_src_scope(dst_type);
 	dst.label = ipv6_addr_label(daddr, dst_type, dst.ifindex);
+	dst.prefs = prefs;
 
 	hiscore->rule = -1;
 	hiscore->ifa = NULL;
diff --git a/net/ipv6/fib6_rules.c b/net/ipv6/fib6_rules.c
index 5513740..e7a7fe2 100644
--- a/net/ipv6/fib6_rules.c
+++ b/net/ipv6/fib6_rules.c
@@ -84,8 +84,18 @@
 		if ((rule->flags & FIB_RULE_FIND_SADDR) &&
 		    r->src.plen && !(flags & RT6_LOOKUP_F_HAS_SADDR)) {
 			struct in6_addr saddr;
+			unsigned int srcprefs = 0;
+
+			if (flags & RT6_LOOKUP_F_SRCPREF_TMP)
+				srcprefs |= IPV6_PREFER_SRC_TMP;
+			if (flags & RT6_LOOKUP_F_SRCPREF_PUBLIC)
+				srcprefs |= IPV6_PREFER_SRC_PUBLIC;
+			if (flags & RT6_LOOKUP_F_SRCPREF_COA)
+				srcprefs |= IPV6_PREFER_SRC_COA;
+
 			if (ipv6_dev_get_saddr(ip6_dst_idev(&rt->u.dst)->dev,
-					       &flp->fl6_dst, &saddr))
+					       &flp->fl6_dst, srcprefs,
+					       &saddr))
 				goto again;
 			if (!ipv6_prefix_equal(&saddr, &r->src.addr,
 					       r->src.plen))
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 2a4f08c..d34aa61 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -920,7 +920,9 @@
 
 	if (ipv6_addr_any(&fl->fl6_src)) {
 		err = ipv6_dev_get_saddr(ip6_dst_idev(*dst)->dev,
-					 &fl->fl6_dst, &fl->fl6_src);
+					 &fl->fl6_dst,
+					 sk ? inet6_sk(sk)->srcprefs : 0,
+					 &fl->fl6_src);
 		if (err)
 			goto out_err_release;
 	}
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index 8e29fb1..dc6695c 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -617,7 +617,67 @@
 		retv = xfrm_user_policy(sk, optname, optval, optlen);
 		break;
 
+	case IPV6_ADDR_PREFERENCES:
+	    {
+		unsigned int pref = 0;
+		unsigned int prefmask = ~0;
+
+		retv = -EINVAL;
+
+		/* check PUBLIC/TMP/PUBTMP_DEFAULT conflicts */
+		switch (val & (IPV6_PREFER_SRC_PUBLIC|
+			       IPV6_PREFER_SRC_TMP|
+			       IPV6_PREFER_SRC_PUBTMP_DEFAULT)) {
+		case IPV6_PREFER_SRC_PUBLIC:
+			pref |= IPV6_PREFER_SRC_PUBLIC;
+			break;
+		case IPV6_PREFER_SRC_TMP:
+			pref |= IPV6_PREFER_SRC_TMP;
+			break;
+		case IPV6_PREFER_SRC_PUBTMP_DEFAULT:
+			break;
+		case 0:
+			goto pref_skip_pubtmp;
+		default:
+			goto e_inval;
+		}
+
+		prefmask &= ~(IPV6_PREFER_SRC_PUBLIC|
+			      IPV6_PREFER_SRC_TMP);
+pref_skip_pubtmp:
+
+		/* check HOME/COA conflicts */
+		switch (val & (IPV6_PREFER_SRC_HOME|IPV6_PREFER_SRC_COA)) {
+		case IPV6_PREFER_SRC_HOME:
+			break;
+		case IPV6_PREFER_SRC_COA:
+			pref |= IPV6_PREFER_SRC_COA;
+		case 0:
+			goto pref_skip_coa;
+		default:
+			goto e_inval;
+		}
+
+		prefmask &= ~IPV6_PREFER_SRC_COA;
+pref_skip_coa:
+
+		/* check CGA/NONCGA conflicts */
+		switch (val & (IPV6_PREFER_SRC_CGA|IPV6_PREFER_SRC_NONCGA)) {
+		case IPV6_PREFER_SRC_CGA:
+		case IPV6_PREFER_SRC_NONCGA:
+		case 0:
+			break;
+		default:
+			goto e_inval;
+		}
+
+		np->srcprefs = (np->srcprefs & prefmask) | pref;
+		retv = 0;
+
+		break;
+	    }
 	}
+
 	release_sock(sk);
 
 	return retv;
@@ -932,6 +992,24 @@
 		val = np->sndflow;
 		break;
 
+	case IPV6_ADDR_PREFERENCES:
+		val = 0;
+
+		if (np->srcprefs & IPV6_PREFER_SRC_TMP)
+			val |= IPV6_PREFER_SRC_TMP;
+		else if (np->srcprefs & IPV6_PREFER_SRC_PUBLIC)
+			val |= IPV6_PREFER_SRC_PUBLIC;
+		else {
+			/* XXX: should we return system default? */
+			val |= IPV6_PREFER_SRC_PUBTMP_DEFAULT;
+		}
+
+		if (np->srcprefs & IPV6_PREFER_SRC_COA)
+			val |= IPV6_PREFER_SRC_COA;
+		else
+			val |= IPV6_PREFER_SRC_HOME;
+		break;
+
 	default:
 		return -ENOPROTOOPT;
 	}
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index e7d8e74..3f68a6e 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -546,7 +546,9 @@
 			override = 0;
 		in6_ifa_put(ifp);
 	} else {
-		if (ipv6_dev_get_saddr(dev, daddr, &tmpaddr))
+		if (ipv6_dev_get_saddr(dev, daddr,
+				       inet6_sk(dev->nd_net->ipv6.ndisc_sk)->srcprefs,
+				       &tmpaddr))
 			return;
 		src_addr = &tmpaddr;
 	}
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index aa3f087..06faa46 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -782,6 +782,15 @@
 
 	if (!ipv6_addr_any(&fl->fl6_src))
 		flags |= RT6_LOOKUP_F_HAS_SADDR;
+	else if (sk) {
+		unsigned int prefs = inet6_sk(sk)->srcprefs;
+		if (prefs & IPV6_PREFER_SRC_TMP)
+			flags |= RT6_LOOKUP_F_SRCPREF_TMP;
+		if (prefs & IPV6_PREFER_SRC_PUBLIC)
+			flags |= RT6_LOOKUP_F_SRCPREF_PUBLIC;
+		if (prefs & IPV6_PREFER_SRC_COA)
+			flags |= RT6_LOOKUP_F_SRCPREF_COA;
+	}
 
 	return fib6_rule_lookup(net, fl, flags, ip6_pol_route_output);
 }
@@ -2162,7 +2171,7 @@
 	else if (dst) {
 		struct in6_addr saddr_buf;
 		if (ipv6_dev_get_saddr(ip6_dst_idev(&rt->u.dst)->dev,
-				       dst, &saddr_buf) == 0)
+				       dst, 0, &saddr_buf) == 0)
 			NLA_PUT(skb, RTA_PREFSRC, 16, &saddr_buf);
 	}
 
diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c
index e96dafd..d92d1fc 100644
--- a/net/ipv6/xfrm6_policy.c
+++ b/net/ipv6/xfrm6_policy.c
@@ -58,7 +58,7 @@
 		return -EHOSTUNREACH;
 
 	ipv6_dev_get_saddr(ip6_dst_idev(dst)->dev,
-			   (struct in6_addr *)&daddr->a6,
+			   (struct in6_addr *)&daddr->a6, 0,
 			   (struct in6_addr *)&saddr->a6);
 	dst_release(dst);
 	return 0;