Commit 072047e4 authored by YOSHIFUJI Hideaki's avatar YOSHIFUJI Hideaki Committed by David S. Miller

[IPV6]: RFC3484 compliant source address selection

Choose more appropriate source address; e.g.
 - outgoing interface
 - non-deprecated
 - scope
 - matching label
Signed-off-by: default avatarYOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent b1cacb68
...@@ -809,138 +809,274 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, struct inet6_ifaddr *i ...@@ -809,138 +809,274 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, struct inet6_ifaddr *i
#endif #endif
/* /*
* Choose an appropriate source address * Choose an appropriate source address (RFC3484)
* should do:
* i) get an address with an appropriate scope
* ii) see if there is a specific route for the destination and use
* an address of the attached interface
* iii) don't use deprecated addresses
*/ */
static int inline ipv6_saddr_pref(const struct inet6_ifaddr *ifp, u8 invpref) struct ipv6_saddr_score {
int addr_type;
unsigned int attrs;
int matchlen;
unsigned int scope;
unsigned int rule;
};
#define IPV6_SADDR_SCORE_LOCAL 0x0001
#define IPV6_SADDR_SCORE_PREFERRED 0x0004
#define IPV6_SADDR_SCORE_HOA 0x0008
#define IPV6_SADDR_SCORE_OIF 0x0010
#define IPV6_SADDR_SCORE_LABEL 0x0020
#define IPV6_SADDR_SCORE_PRIVACY 0x0040
static int inline ipv6_saddr_preferred(int type)
{ {
int pref; if (type & (IPV6_ADDR_MAPPED|IPV6_ADDR_COMPATv4|
pref = ifp->flags&IFA_F_DEPRECATED ? 0 : 2; IPV6_ADDR_LOOPBACK|IPV6_ADDR_RESERVED))
#ifdef CONFIG_IPV6_PRIVACY return 1;
pref |= (ifp->flags^invpref)&IFA_F_TEMPORARY ? 0 : 1; return 0;
#endif
return pref;
} }
#ifdef CONFIG_IPV6_PRIVACY /* static matching label */
#define IPV6_GET_SADDR_MAXSCORE(score) ((score) == 3) static int inline ipv6_saddr_label(const struct in6_addr *addr, int type)
#else {
#define IPV6_GET_SADDR_MAXSCORE(score) (score) /*
#endif * prefix (longest match) label
* -----------------------------
* ::1/128 0
* ::/0 1
* 2002::/16 2
* ::/96 3
* ::ffff:0:0/96 4
*/
if (type & IPV6_ADDR_LOOPBACK)
return 0;
else if (type & IPV6_ADDR_COMPATv4)
return 3;
else if (type & IPV6_ADDR_MAPPED)
return 4;
else if (addr->s6_addr16[0] == htons(0x2002))
return 2;
return 1;
}
int ipv6_dev_get_saddr(struct net_device *dev, int ipv6_dev_get_saddr(struct net_device *daddr_dev,
struct in6_addr *daddr, struct in6_addr *saddr) struct in6_addr *daddr, struct in6_addr *saddr)
{ {
struct inet6_ifaddr *ifp = NULL; struct ipv6_saddr_score hiscore;
struct inet6_ifaddr *match = NULL; struct inet6_ifaddr *ifa_result = NULL;
struct inet6_dev *idev; int daddr_type = __ipv6_addr_type(daddr);
int scope; int daddr_scope = __ipv6_addr_src_scope(daddr_type);
int err; u32 daddr_label = ipv6_saddr_label(daddr, daddr_type);
int hiscore = -1, score; struct net_device *dev;
scope = ipv6_addr_scope(daddr); memset(&hiscore, 0, sizeof(hiscore));
/* read_lock(&dev_base_lock);
* known dev read_lock(&addrconf_lock);
* search dev and walk through dev addresses
*/
if (dev) { for (dev = dev_base; dev; dev=dev->next) {
if (dev->flags & IFF_LOOPBACK) struct inet6_dev *idev;
scope = IFA_HOST; struct inet6_ifaddr *ifa;
/* Rule 0: Candidate Source Address (section 4)
* - multicast and link-local destination address,
* the set of candidate source address MUST only
* include addresses assigned to interfaces
* belonging to the same link as the outgoing
* interface.
* (- For site-local destination addresses, the
* set of candidate source addresses MUST only
* include addresses assigned to interfaces
* belonging to the same site as the outgoing
* interface.)
*/
if ((daddr_type & IPV6_ADDR_MULTICAST ||
daddr_scope <= IPV6_ADDR_SCOPE_LINKLOCAL) &&
daddr_dev && dev != daddr_dev)
continue;
read_lock(&addrconf_lock);
idev = __in6_dev_get(dev); idev = __in6_dev_get(dev);
if (idev) { if (!idev)
read_lock_bh(&idev->lock); continue;
for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
if (ifp->scope == scope) {
if (ifp->flags&IFA_F_TENTATIVE)
continue;
#ifdef CONFIG_IPV6_PRIVACY
score = ipv6_saddr_pref(ifp, idev->cnf.use_tempaddr > 1 ? IFA_F_TEMPORARY : 0);
#else
score = ipv6_saddr_pref(ifp, 0);
#endif
if (score <= hiscore)
continue;
if (match) read_lock_bh(&idev->lock);
in6_ifa_put(match); for (ifa = idev->addr_list; ifa; ifa = ifa->if_next) {
match = ifp; struct ipv6_saddr_score score;
hiscore = score;
in6_ifa_hold(ifp);
if (IPV6_GET_SADDR_MAXSCORE(score)) { score.addr_type = __ipv6_addr_type(&ifa->addr);
read_unlock_bh(&idev->lock);
read_unlock(&addrconf_lock); /* Rule 0: Candidate Source Address (section 4)
goto out; * - In any case, anycast addresses, multicast
} * addresses, and the unspecified address MUST
* NOT be included in a candidate set.
*/
if (unlikely(score.addr_type == IPV6_ADDR_ANY ||
score.addr_type & IPV6_ADDR_MULTICAST)) {
LIMIT_NETDEBUG(KERN_DEBUG
"ADDRCONF: unspecified / multicast address"
"assigned as unicast address on %s",
dev->name);
continue;
}
score.attrs = 0;
score.matchlen = 0;
score.scope = 0;
score.rule = 0;
if (ifa_result == NULL) {
/* record it if the first available entry */
goto record_it;
}
/* Rule 1: Prefer same address */
if (hiscore.rule < 1) {
if (ipv6_addr_equal(&ifa_result->addr, daddr))
hiscore.attrs |= IPV6_SADDR_SCORE_LOCAL;
hiscore.rule++;
}
if (ipv6_addr_equal(&ifa->addr, daddr)) {
score.attrs |= IPV6_SADDR_SCORE_LOCAL;
if (!(hiscore.attrs & IPV6_SADDR_SCORE_LOCAL)) {
score.rule = 1;
goto record_it;
} }
} else {
if (hiscore.attrs & IPV6_SADDR_SCORE_LOCAL)
continue;
} }
read_unlock_bh(&idev->lock);
}
read_unlock(&addrconf_lock);
}
if (scope == IFA_LINK) /* Rule 2: Prefer appropriate scope */
goto out; if (hiscore.rule < 2) {
hiscore.scope = __ipv6_addr_src_scope(hiscore.addr_type);
hiscore.rule++;
}
score.scope = __ipv6_addr_src_scope(score.addr_type);
if (hiscore.scope < score.scope) {
if (hiscore.scope < daddr_scope) {
score.rule = 2;
goto record_it;
} else
continue;
} else if (score.scope < hiscore.scope) {
if (score.scope < daddr_scope)
continue;
else {
score.rule = 2;
goto record_it;
}
}
/* /* Rule 3: Avoid deprecated address */
* dev == NULL or search failed for specified dev if (hiscore.rule < 3) {
*/ if (ipv6_saddr_preferred(hiscore.addr_type) ||
!(ifa_result->flags & IFA_F_DEPRECATED))
hiscore.attrs |= IPV6_SADDR_SCORE_PREFERRED;
hiscore.rule++;
}
if (ipv6_saddr_preferred(score.addr_type) ||
!(ifa->flags & IFA_F_DEPRECATED)) {
score.attrs |= IPV6_SADDR_SCORE_PREFERRED;
if (!(hiscore.attrs & IPV6_SADDR_SCORE_PREFERRED)) {
score.rule = 3;
goto record_it;
}
} else {
if (hiscore.attrs & IPV6_SADDR_SCORE_PREFERRED)
continue;
}
read_lock(&dev_base_lock); /* Rule 4: Prefer home address -- not implemented yet */
read_lock(&addrconf_lock);
for (dev = dev_base; dev; dev=dev->next) {
idev = __in6_dev_get(dev);
if (idev) {
read_lock_bh(&idev->lock);
for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
if (ifp->scope == scope) {
if (ifp->flags&IFA_F_TENTATIVE)
continue;
#ifdef CONFIG_IPV6_PRIVACY
score = ipv6_saddr_pref(ifp, idev->cnf.use_tempaddr > 1 ? IFA_F_TEMPORARY : 0);
#else
score = ipv6_saddr_pref(ifp, 0);
#endif
if (score <= hiscore)
continue;
if (match) /* Rule 5: Prefer outgoing interface */
in6_ifa_put(match); if (hiscore.rule < 5) {
match = ifp; if (daddr_dev == NULL ||
hiscore = score; daddr_dev == ifa_result->idev->dev)
in6_ifa_hold(ifp); hiscore.attrs |= IPV6_SADDR_SCORE_OIF;
hiscore.rule++;
}
if (daddr_dev == NULL ||
daddr_dev == ifa->idev->dev) {
score.attrs |= IPV6_SADDR_SCORE_OIF;
if (!(hiscore.attrs & IPV6_SADDR_SCORE_OIF)) {
score.rule = 5;
goto record_it;
}
} else {
if (hiscore.attrs & IPV6_SADDR_SCORE_OIF)
continue;
}
if (IPV6_GET_SADDR_MAXSCORE(score)) { /* Rule 6: Prefer matching label */
read_unlock_bh(&idev->lock); if (hiscore.rule < 6) {
goto out_unlock_base; if (ipv6_saddr_label(&ifa_result->addr, hiscore.addr_type) == daddr_label)
} hiscore.attrs |= IPV6_SADDR_SCORE_LABEL;
hiscore.rule++;
}
if (ipv6_saddr_label(&ifa->addr, score.addr_type) == daddr_label) {
score.attrs |= IPV6_SADDR_SCORE_LABEL;
if (!(hiscore.attrs & IPV6_SADDR_SCORE_LABEL)) {
score.rule = 6;
goto record_it;
} }
} else {
if (hiscore.attrs & IPV6_SADDR_SCORE_LABEL)
continue;
} }
read_unlock_bh(&idev->lock);
/* Rule 7: Prefer public address
* Note: prefer temprary address if use_tempaddr >= 2
*/
if (hiscore.rule < 7) {
if ((!(ifa_result->flags & IFA_F_TEMPORARY)) ^
(ifa_result->idev->cnf.use_tempaddr >= 2))
hiscore.attrs |= IPV6_SADDR_SCORE_PRIVACY;
hiscore.rule++;
}
if ((!(ifa->flags & IFA_F_TEMPORARY)) ^
(ifa->idev->cnf.use_tempaddr >= 2)) {
score.attrs |= IPV6_SADDR_SCORE_PRIVACY;
if (!(hiscore.attrs & IPV6_SADDR_SCORE_PRIVACY)) {
score.rule = 7;
goto record_it;
}
} else {
if (hiscore.attrs & IPV6_SADDR_SCORE_PRIVACY)
continue;
}
/* Rule 8: Use longest matching prefix */
if (hiscore.rule < 8)
hiscore.matchlen = ipv6_addr_diff(&ifa_result->addr, daddr);
score.rule++;
score.matchlen = ipv6_addr_diff(&ifa->addr, daddr);
if (score.matchlen > hiscore.matchlen) {
score.rule = 8;
goto record_it;
}
#if 0
else if (score.matchlen < hiscore.matchlen)
continue;
#endif
/* Final Rule: choose first available one */
continue;
record_it:
if (ifa_result)
in6_ifa_put(ifa_result);
in6_ifa_hold(ifa);
ifa_result = ifa;
hiscore = score;
} }
read_unlock_bh(&idev->lock);
} }
out_unlock_base:
read_unlock(&addrconf_lock); read_unlock(&addrconf_lock);
read_unlock(&dev_base_lock); read_unlock(&dev_base_lock);
out: if (!ifa_result)
err = -EADDRNOTAVAIL; return -EADDRNOTAVAIL;
if (match) {
ipv6_addr_copy(saddr, &match->addr); ipv6_addr_copy(saddr, &ifa_result->addr);
err = 0; in6_ifa_put(ifa_result);
in6_ifa_put(match); return 0;
}
return err;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment