[IPV6] ADDRCONF: Clean-up ipv6_dev_get_saddr().

old:
|    text	   data	    bss	    dec	    hex	filename
|   28599	   1416	     96	  30111	   759f	net/ipv6/addrconf.o

new:
|    text	   data	    bss	    dec	    hex	filename
|   28007	   1416	     96	  29519	   734f	net/ipv6/addrconf.o

Signed-off-by: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
This commit is contained in:
YOSHIFUJI Hideaki 2008-03-02 10:48:21 +09:00
parent 9bb182a700
commit a9b05723ff

View file

@ -877,20 +877,39 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, struct inet6_ifaddr *i
/*
* Choose an appropriate source address (RFC3484)
*/
struct ipv6_saddr_score {
int addr_type;
unsigned int attrs;
int matchlen;
int scope;
unsigned int rule;
enum {
IPV6_SADDR_RULE_INIT = 0,
IPV6_SADDR_RULE_LOCAL,
IPV6_SADDR_RULE_SCOPE,
IPV6_SADDR_RULE_PREFERRED,
#ifdef CONFIG_IPV6_MIP6
IPV6_SADDR_RULE_HOA,
#endif
IPV6_SADDR_RULE_OIF,
IPV6_SADDR_RULE_LABEL,
#ifdef CONFIG_IPV6_PRIVACY
IPV6_SADDR_RULE_PRIVACY,
#endif
IPV6_SADDR_RULE_ORCHID,
IPV6_SADDR_RULE_PREFIX,
IPV6_SADDR_RULE_MAX
};
#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
struct ipv6_saddr_score {
int rule;
int addr_type;
struct inet6_ifaddr *ifa;
DECLARE_BITMAP(scorebits, IPV6_SADDR_RULE_MAX);
int scopedist;
int matchlen;
};
struct ipv6_saddr_dst {
struct in6_addr *addr;
int ifindex;
int scope;
int label;
};
static inline int ipv6_saddr_preferred(int type)
{
@ -900,28 +919,142 @@ static inline int ipv6_saddr_preferred(int type)
return 0;
}
int ipv6_dev_get_saddr(struct net_device *daddr_dev,
static int ipv6_get_saddr_eval(struct ipv6_saddr_score *score,
struct ipv6_saddr_dst *dst,
int i)
{
int ret;
if (i <= score->rule) {
switch (i) {
case IPV6_SADDR_RULE_SCOPE:
ret = score->scopedist;
break;
case IPV6_SADDR_RULE_PREFIX:
ret = score->matchlen;
break;
default:
ret = !!test_bit(i, score->scorebits);
}
goto out;
}
switch (i) {
case IPV6_SADDR_RULE_INIT:
/* Rule 0: remember if hiscore is not ready yet */
ret = !!score->ifa;
break;
case IPV6_SADDR_RULE_LOCAL:
/* Rule 1: Prefer same address */
ret = ipv6_addr_equal(&score->ifa->addr, dst->addr);
break;
case IPV6_SADDR_RULE_SCOPE:
/* Rule 2: Prefer appropriate scope
*
* ret
* ^
* -1 | d 15
* ---+--+-+---> scope
* |
* | d is scope of the destination.
* B-d | \
* | \ <- smaller scope is better if
* B-15 | \ if scope is enough for destinaion.
* | ret = B - scope (-1 <= scope >= d <= 15).
* d-C-1 | /
* |/ <- greater is better
* -C / if scope is not enough for destination.
* /| ret = scope - C (-1 <= d < scope <= 15).
*
* d - C - 1 < B -15 (for all -1 <= d <= 15).
* C > d + 14 - B >= 15 + 14 - B = 29 - B.
* Assume B = 0 and we get C > 29.
*/
ret = __ipv6_addr_src_scope(score->addr_type);
if (ret >= dst->scope)
ret = -ret;
else
ret -= 128; /* 30 is enough */
score->scopedist = ret;
break;
case IPV6_SADDR_RULE_PREFERRED:
/* Rule 3: Avoid deprecated and optimistic addresses */
ret = ipv6_saddr_preferred(score->addr_type) ||
!(score->ifa->flags & (IFA_F_DEPRECATED|IFA_F_OPTIMISTIC));
break;
#ifdef CONFIG_IPV6_MIP6
case IPV6_SADDR_RULE_HOA:
/* Rule 4: Prefer home address */
ret = !!(score->ifa->flags & IFA_F_HOMEADDRESS);
break;
#endif
case IPV6_SADDR_RULE_OIF:
/* Rule 5: Prefer outgoing interface */
ret = (!dst->ifindex ||
dst->ifindex == score->ifa->idev->dev->ifindex);
break;
case IPV6_SADDR_RULE_LABEL:
/* Rule 6: Prefer matching label */
ret = ipv6_addr_label(&score->ifa->addr, score->addr_type,
score->ifa->idev->dev->ifindex) == dst->label;
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);
break;
#endif
case IPV6_SADDR_RULE_ORCHID:
/* Rule 8-: Prefer ORCHID vs ORCHID or
* non-ORCHID vs non-ORCHID
*/
ret = !(ipv6_addr_orchid(&score->ifa->addr) ^
ipv6_addr_orchid(dst->addr));
break;
case IPV6_SADDR_RULE_PREFIX:
/* Rule 8: Use longest matching prefix */
score->matchlen = ret = ipv6_addr_diff(&score->ifa->addr,
dst->addr);
break;
default:
ret = 0;
}
if (ret)
__set_bit(i, score->scorebits);
score->rule = i;
out:
return ret;
}
int ipv6_dev_get_saddr(struct net_device *dst_dev,
struct in6_addr *daddr, struct in6_addr *saddr)
{
struct ipv6_saddr_score hiscore;
struct inet6_ifaddr *ifa_result = NULL;
struct net *net = daddr_dev->nd_net;
int daddr_type = __ipv6_addr_type(daddr);
int daddr_scope = __ipv6_addr_src_scope(daddr_type);
int daddr_ifindex = daddr_dev ? daddr_dev->ifindex : 0;
u32 daddr_label = ipv6_addr_label(daddr, daddr_type, daddr_ifindex);
struct ipv6_saddr_score scores[2],
*score = &scores[0], *hiscore = &scores[1];
struct net *net = dst_dev->nd_net;
struct ipv6_saddr_dst dst;
struct net_device *dev;
int dst_type;
memset(&hiscore, 0, sizeof(hiscore));
dst_type = __ipv6_addr_type(daddr);
dst.addr = daddr;
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);
hiscore->rule = -1;
hiscore->ifa = NULL;
read_lock(&dev_base_lock);
rcu_read_lock();
for_each_netdev(net, dev) {
struct inet6_dev *idev;
struct inet6_ifaddr *ifa;
/* Rule 0: Candidate Source Address (section 4)
/* Candidate Source Address (section 4)
* - multicast and link-local destination address,
* the set of candidate source address MUST only
* include addresses assigned to interfaces
@ -933,9 +1066,9 @@ int ipv6_dev_get_saddr(struct net_device *daddr_dev,
* 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)
if (((dst_type & IPV6_ADDR_MULTICAST) ||
dst.scope <= IPV6_ADDR_SCOPE_LINKLOCAL) &&
dst.ifindex && dev->ifindex != dst.ifindex)
continue;
idev = __in6_dev_get(dev);
@ -943,12 +1076,10 @@ int ipv6_dev_get_saddr(struct net_device *daddr_dev,
continue;
read_lock_bh(&idev->lock);
for (ifa = idev->addr_list; ifa; ifa = ifa->if_next) {
struct ipv6_saddr_score score;
for (score->ifa = idev->addr_list; score->ifa; score->ifa = score->ifa->if_next) {
int i;
score.addr_type = __ipv6_addr_type(&ifa->addr);
/* Rule 0:
/*
* - Tentative Address (RFC2462 section 5.4)
* - A tentative address is not considered
* "assigned to an interface" in the traditional
@ -958,11 +1089,14 @@ int ipv6_dev_get_saddr(struct net_device *daddr_dev,
* addresses, and the unspecified address MUST
* NOT be included in a candidate set.
*/
if ((ifa->flags & IFA_F_TENTATIVE) &&
(!(ifa->flags & IFA_F_OPTIMISTIC)))
if ((score->ifa->flags & IFA_F_TENTATIVE) &&
(!(score->ifa->flags & IFA_F_OPTIMISTIC)))
continue;
if (unlikely(score.addr_type == IPV6_ADDR_ANY ||
score.addr_type & IPV6_ADDR_MULTICAST)) {
score->addr_type = __ipv6_addr_type(&score->ifa->addr);
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",
@ -970,201 +1104,59 @@ int ipv6_dev_get_saddr(struct net_device *daddr_dev,
continue;
}
score.attrs = 0;
score.matchlen = 0;
score.scope = 0;
score.rule = 0;
score->rule = -1;
bitmap_zero(score->scorebits, IPV6_SADDR_RULE_MAX);
if (ifa_result == NULL) {
/* record it if the first available entry */
goto record_it;
}
for (i = 0; i < IPV6_SADDR_RULE_MAX; i++) {
int minihiscore, miniscore;
/* 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;
}
minihiscore = ipv6_get_saddr_eval(hiscore, &dst, i);
miniscore = ipv6_get_saddr_eval(score, &dst, i);
/* Rule 2: Prefer appropriate scope */
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)
break; /* addresses sorted by scope */
else {
score.rule = 2;
goto record_it;
if (minihiscore > miniscore) {
if (i == IPV6_SADDR_RULE_SCOPE &&
score->scopedist > 0) {
/*
* special case:
* each remaining entry
* has too small (not enough)
* scope, because ifa entries
* are sorted by their scope
* values.
*/
goto try_nextdev;
}
break;
} else if (minihiscore < miniscore) {
struct ipv6_saddr_score *tmp;
if (hiscore->ifa)
in6_ifa_put(hiscore->ifa);
in6_ifa_hold(score->ifa);
tmp = hiscore;
hiscore = score;
score = tmp;
/* restore our iterator */
score->ifa = hiscore->ifa;
break;
}
}
/* Rule 3: Avoid deprecated and optimistic addresses */
if (hiscore.rule < 3) {
if (ipv6_saddr_preferred(hiscore.addr_type) ||
(((ifa_result->flags &
(IFA_F_DEPRECATED|IFA_F_OPTIMISTIC)) == 0)))
hiscore.attrs |= IPV6_SADDR_SCORE_PREFERRED;
hiscore.rule++;
}
if (ipv6_saddr_preferred(score.addr_type) ||
(((ifa->flags &
(IFA_F_DEPRECATED|IFA_F_OPTIMISTIC)) == 0))) {
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;
}
/* Rule 4: Prefer home address */
#if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
if (hiscore.rule < 4) {
if (ifa_result->flags & IFA_F_HOMEADDRESS)
hiscore.attrs |= IPV6_SADDR_SCORE_HOA;
hiscore.rule++;
}
if (ifa->flags & IFA_F_HOMEADDRESS) {
score.attrs |= IPV6_SADDR_SCORE_HOA;
if (!(ifa_result->flags & IFA_F_HOMEADDRESS)) {
score.rule = 4;
goto record_it;
}
} else {
if (hiscore.attrs & IPV6_SADDR_SCORE_HOA)
continue;
}
#else
if (hiscore.rule < 4)
hiscore.rule++;
#endif
/* Rule 5: Prefer outgoing interface */
if (hiscore.rule < 5) {
if (daddr_dev == NULL ||
daddr_dev == ifa_result->idev->dev)
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;
}
/* Rule 6: Prefer matching label */
if (hiscore.rule < 6) {
if (ipv6_addr_label(&ifa_result->addr,
hiscore.addr_type,
ifa_result->idev->dev->ifindex) == daddr_label)
hiscore.attrs |= IPV6_SADDR_SCORE_LABEL;
hiscore.rule++;
}
if (ipv6_addr_label(&ifa->addr,
score.addr_type,
ifa->idev->dev->ifindex) == 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;
}
#ifdef CONFIG_IPV6_PRIVACY
/* 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;
}
#else
if (hiscore.rule < 7)
hiscore.rule++;
#endif
/* Skip rule 8 for orchid -> non-orchid address pairs. */
if (ipv6_addr_orchid(&ifa->addr) && !ipv6_addr_orchid(daddr))
continue;
/* Rule 8: Use longest matching prefix */
if (hiscore.rule < 8) {
hiscore.matchlen = ipv6_addr_diff(&ifa_result->addr, daddr);
hiscore.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;
}
try_nextdev:
read_unlock_bh(&idev->lock);
}
rcu_read_unlock();
read_unlock(&dev_base_lock);
if (!ifa_result)
if (!hiscore->ifa)
return -EADDRNOTAVAIL;
ipv6_addr_copy(saddr, &ifa_result->addr);
in6_ifa_put(ifa_result);
ipv6_addr_copy(saddr, &hiscore->ifa->addr);
in6_ifa_put(hiscore->ifa);
return 0;
}