summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/linux/in6.h11
-rw-r--r--include/linux/ipv6.h6
-rw-r--r--include/net/addrconf.h1
-rw-r--r--include/net/ip6_route.h9
-rw-r--r--net/ipv6/addrconf.c17
-rw-r--r--net/ipv6/fib6_rules.c12
-rw-r--r--net/ipv6/ip6_output.c4
-rw-r--r--net/ipv6/ipv6_sockglue.c78
-rw-r--r--net/ipv6/ndisc.c4
-rw-r--r--net/ipv6/route.c11
-rw-r--r--net/ipv6/xfrm6_policy.c2
-rw-r--r--net/sctp/ipv6.c4
12 files changed, 146 insertions, 13 deletions
diff --git a/include/linux/in6.h b/include/linux/in6.h
index 2a61c82af11..f674000c6c9 100644
--- a/include/linux/in6.h
+++ b/include/linux/in6.h
@@ -249,4 +249,15 @@ struct in6_flowlabel_req
* IP6T_SO_GET_REVISION_TARGET 69
*/
+/* RFC5014: Source address selection */
+#define IPV6_ADDR_PREFERENCES 72
+
+#define IPV6_PREFER_SRC_TMP 0x0001
+#define IPV6_PREFER_SRC_PUBLIC 0x0002
+#define IPV6_PREFER_SRC_PUBTMP_DEFAULT 0x0100
+#define IPV6_PREFER_SRC_COA 0x0004
+#define IPV6_PREFER_SRC_HOME 0x0400
+#define IPV6_PREFER_SRC_CGA 0x0008
+#define IPV6_PREFER_SRC_NONCGA 0x0800
+
#endif
diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
index 87ae4e389ce..c9ba0da16ce 100644
--- a/include/linux/ipv6.h
+++ b/include/linux/ipv6.h
@@ -322,7 +322,11 @@ struct ipv6_pinfo {
__u8 recverr:1,
sndflow:1,
pmtudisc:2,
- ipv6only:1;
+ ipv6only:1,
+ srcprefs:3; /* 001: prefer temporary address
+ * 010: prefer public address
+ * 100: prefer care-of address
+ */
__u8 tclass;
__u32 dst_cookie;
diff --git a/include/net/addrconf.h b/include/net/addrconf.h
index edcb4bbaab7..c9276c72764 100644
--- a/include/net/addrconf.h
+++ b/include/net/addrconf.h
@@ -78,6 +78,7 @@ extern struct inet6_ifaddr *ipv6_get_ifaddr(struct net *net,
extern int ipv6_dev_get_saddr(struct net_device *dev,
struct in6_addr *daddr,
+ unsigned int srcprefs,
struct in6_addr *saddr);
extern int ipv6_get_lladdr(struct net_device *dev,
struct in6_addr *addr,
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 5c3b67c86ae..3ae6799c2b1 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -30,9 +30,12 @@ struct route_info {
#include <linux/ip.h>
#include <linux/ipv6.h>
-#define RT6_LOOKUP_F_IFACE 0x1
-#define RT6_LOOKUP_F_REACHABLE 0x2
-#define RT6_LOOKUP_F_HAS_SADDR 0x4
+#define RT6_LOOKUP_F_IFACE 0x00000001
+#define RT6_LOOKUP_F_REACHABLE 0x00000002
+#define RT6_LOOKUP_F_HAS_SADDR 0x00000004
+#define RT6_LOOKUP_F_SRCPREF_TMP 0x00000008
+#define RT6_LOOKUP_F_SRCPREF_PUBLIC 0x00000010
+#define RT6_LOOKUP_F_SRCPREF_COA 0x00000020
extern struct rt6_info *ip6_null_entry;
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 787e90af166..89954885dee 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -909,6 +909,7 @@ struct ipv6_saddr_dst {
int ifindex;
int scope;
int label;
+ unsigned int prefs;
};
static inline int ipv6_saddr_preferred(int type)
@@ -984,9 +985,12 @@ static int ipv6_get_saddr_eval(struct ipv6_saddr_score *score,
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 @@ static int ipv6_get_saddr_eval(struct ipv6_saddr_score *score,
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 @@ out:
}
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 @@ int ipv6_dev_get_saddr(struct net_device *dst_dev,
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 55137408f05..e7a7fe26ceb 100644
--- a/net/ipv6/fib6_rules.c
+++ b/net/ipv6/fib6_rules.c
@@ -84,8 +84,18 @@ static int fib6_rule_action(struct fib_rule *rule, struct flowi *flp,
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 2a4f08c8a02..d34aa61353b 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -920,7 +920,9 @@ static int ip6_dst_lookup_tail(struct sock *sk,
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 8e29fb1d1df..dc6695cc576 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -617,7 +617,67 @@ done:
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 @@ static int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
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 e7d8e74704c..3f68a6eae7b 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -546,7 +546,9 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
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 aa3f08718e4..06faa46920e 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -782,6 +782,15 @@ struct dst_entry * ip6_route_output(struct net *net, struct sock *sk,
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 @@ static int rt6_fill_node(struct sk_buff *skb, struct rt6_info *rt,
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 e96dafdc703..d92d1fceb8c 100644
--- a/net/ipv6/xfrm6_policy.c
+++ b/net/ipv6/xfrm6_policy.c
@@ -58,7 +58,7 @@ static int xfrm6_get_saddr(xfrm_address_t *saddr, xfrm_address_t *daddr)
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;
diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c
index 46c5b3c5cb9..dc71d0d8375 100644
--- a/net/sctp/ipv6.c
+++ b/net/sctp/ipv6.c
@@ -316,7 +316,9 @@ static void sctp_v6_get_saddr(struct sctp_association *asoc,
if (!asoc) {
ipv6_dev_get_saddr(dst ? ip6_dst_idev(dst)->dev : NULL,
- &daddr->v6.sin6_addr, &saddr->v6.sin6_addr);
+ &daddr->v6.sin6_addr,
+ inet6_sk(asoc->base.sk)->srcprefs,
+ &saddr->v6.sin6_addr);
SCTP_DEBUG_PRINTK("saddr from ipv6_get_saddr: " NIP6_FMT "\n",
NIP6(saddr->v6.sin6_addr));
return;