Commit b7b1bfce authored by Hannes Frederic Sowa's avatar Hannes Frederic Sowa Committed by David S. Miller

ipv6: split duplicate address detection and router solicitation timer

This patch splits the timers for duplicate address detection and router
solicitations apart. The router solicitations timer goes into inet6_dev
and the dad timer stays in inet6_ifaddr.

The reason behind this patch is to reduce the number of unneeded router
solicitations send out by the host if additional link-local addresses
are created. Currently we send out RS for every link-local address on
an interface.

If the RS timer fires we pick a source address with ipv6_get_lladdr. This
change could hurt people adding additional link-local addresses and
specifying these addresses in the radvd clients section because we
no longer guarantee that we use every ll address as source address in
router solicitations.

Cc: Flavio Leitner <fleitner@redhat.com>
Cc: Hideaki YOSHIFUJI <yoshfuji@linux-ipv6.org>
Cc: David Stevens <dlstevens@us.ibm.com>
Signed-off-by: default avatarHannes Frederic Sowa <hannes@stressinduktion.org>
Reviewed-by: default avatarFlavio Leitner <fbl@redhat.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 51151a16
...@@ -50,7 +50,7 @@ struct inet6_ifaddr { ...@@ -50,7 +50,7 @@ struct inet6_ifaddr {
int state; int state;
__u8 probes; __u8 dad_probes;
__u8 flags; __u8 flags;
__u16 scope; __u16 scope;
...@@ -58,7 +58,7 @@ struct inet6_ifaddr { ...@@ -58,7 +58,7 @@ struct inet6_ifaddr {
unsigned long cstamp; /* created timestamp */ unsigned long cstamp; /* created timestamp */
unsigned long tstamp; /* updated timestamp */ unsigned long tstamp; /* updated timestamp */
struct timer_list timer; struct timer_list dad_timer;
struct inet6_dev *idev; struct inet6_dev *idev;
struct rt6_info *rt; struct rt6_info *rt;
...@@ -195,6 +195,10 @@ struct inet6_dev { ...@@ -195,6 +195,10 @@ struct inet6_dev {
struct neigh_parms *nd_parms; struct neigh_parms *nd_parms;
struct ipv6_devconf cnf; struct ipv6_devconf cnf;
struct ipv6_devstat stats; struct ipv6_devstat stats;
struct timer_list rs_timer;
__u8 rs_probes;
unsigned long tstamp; /* ipv6InterfaceTable update timestamp */ unsigned long tstamp; /* ipv6InterfaceTable update timestamp */
struct rcu_head rcu; struct rcu_head rcu;
}; };
......
...@@ -253,37 +253,32 @@ static inline bool addrconf_qdisc_ok(const struct net_device *dev) ...@@ -253,37 +253,32 @@ static inline bool addrconf_qdisc_ok(const struct net_device *dev)
return !qdisc_tx_is_noop(dev); return !qdisc_tx_is_noop(dev);
} }
static void addrconf_del_timer(struct inet6_ifaddr *ifp) static void addrconf_del_rs_timer(struct inet6_dev *idev)
{ {
if (del_timer(&ifp->timer)) if (del_timer(&idev->rs_timer))
__in6_dev_put(idev);
}
static void addrconf_del_dad_timer(struct inet6_ifaddr *ifp)
{
if (del_timer(&ifp->dad_timer))
__in6_ifa_put(ifp); __in6_ifa_put(ifp);
} }
enum addrconf_timer_t { static void addrconf_mod_rs_timer(struct inet6_dev *idev,
AC_NONE, unsigned long when)
AC_DAD, {
AC_RS, if (!timer_pending(&idev->rs_timer))
}; in6_dev_hold(idev);
mod_timer(&idev->rs_timer, jiffies + when);
}
static void addrconf_mod_timer(struct inet6_ifaddr *ifp, static void addrconf_mod_dad_timer(struct inet6_ifaddr *ifp,
enum addrconf_timer_t what,
unsigned long when) unsigned long when)
{ {
if (!del_timer(&ifp->timer)) if (!timer_pending(&ifp->dad_timer))
in6_ifa_hold(ifp); in6_ifa_hold(ifp);
mod_timer(&ifp->dad_timer, jiffies + when);
switch (what) {
case AC_DAD:
ifp->timer.function = addrconf_dad_timer;
break;
case AC_RS:
ifp->timer.function = addrconf_rs_timer;
break;
default:
break;
}
ifp->timer.expires = jiffies + when;
add_timer(&ifp->timer);
} }
static int snmp6_alloc_dev(struct inet6_dev *idev) static int snmp6_alloc_dev(struct inet6_dev *idev)
...@@ -326,6 +321,7 @@ void in6_dev_finish_destroy(struct inet6_dev *idev) ...@@ -326,6 +321,7 @@ void in6_dev_finish_destroy(struct inet6_dev *idev)
WARN_ON(!list_empty(&idev->addr_list)); WARN_ON(!list_empty(&idev->addr_list));
WARN_ON(idev->mc_list != NULL); WARN_ON(idev->mc_list != NULL);
WARN_ON(timer_pending(&idev->rs_timer));
#ifdef NET_REFCNT_DEBUG #ifdef NET_REFCNT_DEBUG
pr_debug("%s: %s\n", __func__, dev ? dev->name : "NIL"); pr_debug("%s: %s\n", __func__, dev ? dev->name : "NIL");
...@@ -357,7 +353,8 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev) ...@@ -357,7 +353,8 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
rwlock_init(&ndev->lock); rwlock_init(&ndev->lock);
ndev->dev = dev; ndev->dev = dev;
INIT_LIST_HEAD(&ndev->addr_list); INIT_LIST_HEAD(&ndev->addr_list);
setup_timer(&ndev->rs_timer, addrconf_rs_timer,
(unsigned long)ndev);
memcpy(&ndev->cnf, dev_net(dev)->ipv6.devconf_dflt, sizeof(ndev->cnf)); memcpy(&ndev->cnf, dev_net(dev)->ipv6.devconf_dflt, sizeof(ndev->cnf));
ndev->cnf.mtu6 = dev->mtu; ndev->cnf.mtu6 = dev->mtu;
ndev->cnf.sysctl = NULL; ndev->cnf.sysctl = NULL;
...@@ -776,7 +773,7 @@ void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp) ...@@ -776,7 +773,7 @@ void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp)
in6_dev_put(ifp->idev); in6_dev_put(ifp->idev);
if (del_timer(&ifp->timer)) if (del_timer(&ifp->dad_timer))
pr_notice("Timer is still running, when freeing ifa=%p\n", ifp); pr_notice("Timer is still running, when freeing ifa=%p\n", ifp);
if (ifp->state != INET6_IFADDR_STATE_DEAD) { if (ifp->state != INET6_IFADDR_STATE_DEAD) {
...@@ -869,9 +866,9 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, int pfxlen, ...@@ -869,9 +866,9 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, int pfxlen,
spin_lock_init(&ifa->lock); spin_lock_init(&ifa->lock);
spin_lock_init(&ifa->state_lock); spin_lock_init(&ifa->state_lock);
init_timer(&ifa->timer); setup_timer(&ifa->dad_timer, addrconf_dad_timer,
(unsigned long)ifa);
INIT_HLIST_NODE(&ifa->addr_lst); INIT_HLIST_NODE(&ifa->addr_lst);
ifa->timer.data = (unsigned long) ifa;
ifa->scope = scope; ifa->scope = scope;
ifa->prefix_len = pfxlen; ifa->prefix_len = pfxlen;
ifa->flags = flags | IFA_F_TENTATIVE; ifa->flags = flags | IFA_F_TENTATIVE;
...@@ -994,7 +991,7 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp) ...@@ -994,7 +991,7 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp)
} }
write_unlock_bh(&idev->lock); write_unlock_bh(&idev->lock);
addrconf_del_timer(ifp); addrconf_del_dad_timer(ifp);
ipv6_ifa_notify(RTM_DELADDR, ifp); ipv6_ifa_notify(RTM_DELADDR, ifp);
...@@ -1447,18 +1444,12 @@ int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev, ...@@ -1447,18 +1444,12 @@ int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,
} }
EXPORT_SYMBOL(ipv6_dev_get_saddr); EXPORT_SYMBOL(ipv6_dev_get_saddr);
int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr, static int __ipv6_get_lladdr(struct inet6_dev *idev, struct in6_addr *addr,
unsigned char banned_flags) unsigned char banned_flags)
{ {
struct inet6_dev *idev;
int err = -EADDRNOTAVAIL;
rcu_read_lock();
idev = __in6_dev_get(dev);
if (idev) {
struct inet6_ifaddr *ifp; struct inet6_ifaddr *ifp;
int err = -EADDRNOTAVAIL;
read_lock_bh(&idev->lock);
list_for_each_entry(ifp, &idev->addr_list, if_list) { list_for_each_entry(ifp, &idev->addr_list, if_list) {
if (ifp->scope == IFA_LINK && if (ifp->scope == IFA_LINK &&
!(ifp->flags & banned_flags)) { !(ifp->flags & banned_flags)) {
...@@ -1467,6 +1458,20 @@ int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr, ...@@ -1467,6 +1458,20 @@ int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr,
break; break;
} }
} }
return err;
}
int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr,
unsigned char banned_flags)
{
struct inet6_dev *idev;
int err = -EADDRNOTAVAIL;
rcu_read_lock();
idev = __in6_dev_get(dev);
if (idev) {
read_lock_bh(&idev->lock);
err = __ipv6_get_lladdr(idev, addr, banned_flags);
read_unlock_bh(&idev->lock); read_unlock_bh(&idev->lock);
} }
rcu_read_unlock(); rcu_read_unlock();
...@@ -1580,7 +1585,7 @@ static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed) ...@@ -1580,7 +1585,7 @@ static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed)
{ {
if (ifp->flags&IFA_F_PERMANENT) { if (ifp->flags&IFA_F_PERMANENT) {
spin_lock_bh(&ifp->lock); spin_lock_bh(&ifp->lock);
addrconf_del_timer(ifp); addrconf_del_dad_timer(ifp);
ifp->flags |= IFA_F_TENTATIVE; ifp->flags |= IFA_F_TENTATIVE;
if (dad_failed) if (dad_failed)
ifp->flags |= IFA_F_DADFAILED; ifp->flags |= IFA_F_DADFAILED;
...@@ -3036,7 +3041,7 @@ static int addrconf_ifdown(struct net_device *dev, int how) ...@@ -3036,7 +3041,7 @@ static int addrconf_ifdown(struct net_device *dev, int how)
hlist_for_each_entry_rcu(ifa, h, addr_lst) { hlist_for_each_entry_rcu(ifa, h, addr_lst) {
if (ifa->idev == idev) { if (ifa->idev == idev) {
hlist_del_init_rcu(&ifa->addr_lst); hlist_del_init_rcu(&ifa->addr_lst);
addrconf_del_timer(ifa); addrconf_del_dad_timer(ifa);
goto restart; goto restart;
} }
} }
...@@ -3045,6 +3050,8 @@ static int addrconf_ifdown(struct net_device *dev, int how) ...@@ -3045,6 +3050,8 @@ static int addrconf_ifdown(struct net_device *dev, int how)
write_lock_bh(&idev->lock); write_lock_bh(&idev->lock);
addrconf_del_rs_timer(idev);
/* Step 2: clear flags for stateless addrconf */ /* Step 2: clear flags for stateless addrconf */
if (!how) if (!how)
idev->if_flags &= ~(IF_RS_SENT|IF_RA_RCVD|IF_READY); idev->if_flags &= ~(IF_RS_SENT|IF_RA_RCVD|IF_READY);
...@@ -3074,7 +3081,7 @@ static int addrconf_ifdown(struct net_device *dev, int how) ...@@ -3074,7 +3081,7 @@ static int addrconf_ifdown(struct net_device *dev, int how)
while (!list_empty(&idev->addr_list)) { while (!list_empty(&idev->addr_list)) {
ifa = list_first_entry(&idev->addr_list, ifa = list_first_entry(&idev->addr_list,
struct inet6_ifaddr, if_list); struct inet6_ifaddr, if_list);
addrconf_del_timer(ifa); addrconf_del_dad_timer(ifa);
list_del(&ifa->if_list); list_del(&ifa->if_list);
...@@ -3116,10 +3123,10 @@ static int addrconf_ifdown(struct net_device *dev, int how) ...@@ -3116,10 +3123,10 @@ static int addrconf_ifdown(struct net_device *dev, int how)
static void addrconf_rs_timer(unsigned long data) static void addrconf_rs_timer(unsigned long data)
{ {
struct inet6_ifaddr *ifp = (struct inet6_ifaddr *) data; struct inet6_dev *idev = (struct inet6_dev *)data;
struct inet6_dev *idev = ifp->idev; struct in6_addr lladdr;
read_lock(&idev->lock); write_lock(&idev->lock);
if (idev->dead || !(idev->if_flags & IF_READY)) if (idev->dead || !(idev->if_flags & IF_READY))
goto out; goto out;
...@@ -3130,18 +3137,19 @@ static void addrconf_rs_timer(unsigned long data) ...@@ -3130,18 +3137,19 @@ static void addrconf_rs_timer(unsigned long data)
if (idev->if_flags & IF_RA_RCVD) if (idev->if_flags & IF_RA_RCVD)
goto out; goto out;
spin_lock(&ifp->lock); if (idev->rs_probes++ < idev->cnf.rtr_solicits) {
if (ifp->probes++ < idev->cnf.rtr_solicits) { if (!__ipv6_get_lladdr(idev, &lladdr, IFA_F_TENTATIVE))
ndisc_send_rs(idev->dev, &lladdr,
&in6addr_linklocal_allrouters);
else
goto out;
/* The wait after the last probe can be shorter */ /* The wait after the last probe can be shorter */
addrconf_mod_timer(ifp, AC_RS, addrconf_mod_rs_timer(idev, (idev->rs_probes ==
(ifp->probes == idev->cnf.rtr_solicits) ? idev->cnf.rtr_solicits) ?
idev->cnf.rtr_solicit_delay : idev->cnf.rtr_solicit_delay :
idev->cnf.rtr_solicit_interval); idev->cnf.rtr_solicit_interval);
spin_unlock(&ifp->lock);
ndisc_send_rs(idev->dev, &ifp->addr, &in6addr_linklocal_allrouters);
} else { } else {
spin_unlock(&ifp->lock);
/* /*
* Note: we do not support deprecated "all on-link" * Note: we do not support deprecated "all on-link"
* assumption any longer. * assumption any longer.
...@@ -3150,8 +3158,8 @@ static void addrconf_rs_timer(unsigned long data) ...@@ -3150,8 +3158,8 @@ static void addrconf_rs_timer(unsigned long data)
} }
out: out:
read_unlock(&idev->lock); write_unlock(&idev->lock);
in6_ifa_put(ifp); in6_dev_put(idev);
} }
/* /*
...@@ -3167,8 +3175,8 @@ static void addrconf_dad_kick(struct inet6_ifaddr *ifp) ...@@ -3167,8 +3175,8 @@ static void addrconf_dad_kick(struct inet6_ifaddr *ifp)
else else
rand_num = net_random() % (idev->cnf.rtr_solicit_delay ? : 1); rand_num = net_random() % (idev->cnf.rtr_solicit_delay ? : 1);
ifp->probes = idev->cnf.dad_transmits; ifp->dad_probes = idev->cnf.dad_transmits;
addrconf_mod_timer(ifp, AC_DAD, rand_num); addrconf_mod_dad_timer(ifp, rand_num);
} }
static void addrconf_dad_start(struct inet6_ifaddr *ifp) static void addrconf_dad_start(struct inet6_ifaddr *ifp)
...@@ -3229,40 +3237,40 @@ static void addrconf_dad_timer(unsigned long data) ...@@ -3229,40 +3237,40 @@ static void addrconf_dad_timer(unsigned long data)
struct inet6_dev *idev = ifp->idev; struct inet6_dev *idev = ifp->idev;
struct in6_addr mcaddr; struct in6_addr mcaddr;
if (!ifp->probes && addrconf_dad_end(ifp)) if (!ifp->dad_probes && addrconf_dad_end(ifp))
goto out; goto out;
read_lock(&idev->lock); write_lock(&idev->lock);
if (idev->dead || !(idev->if_flags & IF_READY)) { if (idev->dead || !(idev->if_flags & IF_READY)) {
read_unlock(&idev->lock); write_unlock(&idev->lock);
goto out; goto out;
} }
spin_lock(&ifp->lock); spin_lock(&ifp->lock);
if (ifp->state == INET6_IFADDR_STATE_DEAD) { if (ifp->state == INET6_IFADDR_STATE_DEAD) {
spin_unlock(&ifp->lock); spin_unlock(&ifp->lock);
read_unlock(&idev->lock); write_unlock(&idev->lock);
goto out; goto out;
} }
if (ifp->probes == 0) { if (ifp->dad_probes == 0) {
/* /*
* DAD was successful * DAD was successful
*/ */
ifp->flags &= ~(IFA_F_TENTATIVE|IFA_F_OPTIMISTIC|IFA_F_DADFAILED); ifp->flags &= ~(IFA_F_TENTATIVE|IFA_F_OPTIMISTIC|IFA_F_DADFAILED);
spin_unlock(&ifp->lock); spin_unlock(&ifp->lock);
read_unlock(&idev->lock); write_unlock(&idev->lock);
addrconf_dad_completed(ifp); addrconf_dad_completed(ifp);
goto out; goto out;
} }
ifp->probes--; ifp->dad_probes--;
addrconf_mod_timer(ifp, AC_DAD, ifp->idev->nd_parms->retrans_time); addrconf_mod_dad_timer(ifp, ifp->idev->nd_parms->retrans_time);
spin_unlock(&ifp->lock); spin_unlock(&ifp->lock);
read_unlock(&idev->lock); write_unlock(&idev->lock);
/* send a neighbour solicitation for our addr */ /* send a neighbour solicitation for our addr */
addrconf_addr_solict_mult(&ifp->addr, &mcaddr); addrconf_addr_solict_mult(&ifp->addr, &mcaddr);
...@@ -3274,6 +3282,9 @@ static void addrconf_dad_timer(unsigned long data) ...@@ -3274,6 +3282,9 @@ static void addrconf_dad_timer(unsigned long data)
static void addrconf_dad_completed(struct inet6_ifaddr *ifp) static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
{ {
struct net_device *dev = ifp->idev->dev; struct net_device *dev = ifp->idev->dev;
struct in6_addr lladdr;
addrconf_del_dad_timer(ifp);
/* /*
* Configure the address for reception. Now it is valid. * Configure the address for reception. Now it is valid.
...@@ -3294,13 +3305,20 @@ static void addrconf_dad_completed(struct inet6_ifaddr *ifp) ...@@ -3294,13 +3305,20 @@ static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
* [...] as part of DAD [...] there is no need * [...] as part of DAD [...] there is no need
* to delay again before sending the first RS * to delay again before sending the first RS
*/ */
ndisc_send_rs(ifp->idev->dev, &ifp->addr, &in6addr_linklocal_allrouters); if (!ipv6_get_lladdr(dev, &lladdr, IFA_F_TENTATIVE))
ndisc_send_rs(dev, &lladdr,
&in6addr_linklocal_allrouters);
else
return;
spin_lock_bh(&ifp->lock); write_lock_bh(&ifp->idev->lock);
ifp->probes = 1; spin_lock(&ifp->lock);
ifp->idev->rs_probes = 1;
ifp->idev->if_flags |= IF_RS_SENT; ifp->idev->if_flags |= IF_RS_SENT;
addrconf_mod_timer(ifp, AC_RS, ifp->idev->cnf.rtr_solicit_interval); addrconf_mod_rs_timer(ifp->idev,
spin_unlock_bh(&ifp->lock); ifp->idev->cnf.rtr_solicit_interval);
spin_unlock(&ifp->lock);
write_unlock_bh(&ifp->idev->lock);
} }
} }
......
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