[IPV6]: Support Source Address Selection API (RFC5014).
YOSHIFUJI Hideaki [Tue, 25 Mar 2008 00:37:42 +0000 (09:37 +0900)]
Signed-off-by: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>

12 files changed:
include/linux/in6.h
include/linux/ipv6.h
include/net/addrconf.h
include/net/ip6_route.h
net/ipv6/addrconf.c
net/ipv6/fib6_rules.c
net/ipv6/ip6_output.c
net/ipv6/ipv6_sockglue.c
net/ipv6/ndisc.c
net/ipv6/route.c
net/ipv6/xfrm6_policy.c
net/sctp/ipv6.c

index 2a61c82..f674000 100644 (file)
@@ -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
index 87ae4e3..c9ba0da 100644 (file)
@@ -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;
index edcb4bb..c9276c7 100644 (file)
@@ -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,
index 5c3b67c..3ae6799 100644 (file)
@@ -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;
 
index 787e90a..8995488 100644 (file)
@@ -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;
index 5513740..e7a7fe2 100644 (file)
@@ -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))
index 2a4f08c..d34aa61 100644 (file)
@@ -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;
        }
index 8e29fb1..dc6695c 100644 (file)
@@ -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;
        }
index e7d8e74..3f68a6e 100644 (file)
@@ -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;
        }
index aa3f087..06faa46 100644 (file)
@@ -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);
        }
 
index e96dafd..d92d1fc 100644 (file)
@@ -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;
index 46c5b3c..dc71d0d 100644 (file)
@@ -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;