Commit 973d5956 authored by Hannes Frederic Sowa's avatar Hannes Frederic Sowa Committed by Willy Tarreau

ipv6: split duplicate address detection and router solicitation timer

commit b7b1bfce upstream.

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>
Cc: <stable@vger.kernel.org> # 3.10.y
[Mike Manning <mmanning@brocade.com>: resolved conflicts with 36bddb]
Signed-off-by: default avatarMike Manning <mmanning@brocade.com>
Signed-off-by: default avatarWilly Tarreau <w@1wt.eu>
parent af80b973
......@@ -50,7 +50,7 @@ struct inet6_ifaddr {
int state;
__u8 probes;
__u8 dad_probes;
__u8 flags;
__u16 scope;
......@@ -58,7 +58,7 @@ struct inet6_ifaddr {
unsigned long cstamp; /* created timestamp */
unsigned long tstamp; /* updated timestamp */
struct timer_list timer;
struct timer_list dad_timer;
struct inet6_dev *idev;
struct rt6_info *rt;
......@@ -195,6 +195,10 @@ struct inet6_dev {
struct inet6_dev *next;
struct ipv6_devconf cnf;
struct ipv6_devstat stats;
struct timer_list rs_timer;
__u8 rs_probes;
unsigned long tstamp; /* ipv6InterfaceTable update timestamp */
struct rcu_head rcu;
};
......
......@@ -253,37 +253,32 @@ static inline bool addrconf_qdisc_ok(const struct net_device *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);
}
enum addrconf_timer_t {
AC_NONE,
AC_DAD,
AC_RS,
};
static void addrconf_mod_rs_timer(struct inet6_dev *idev,
unsigned long when)
{
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,
enum addrconf_timer_t what,
unsigned long when)
static void addrconf_mod_dad_timer(struct inet6_ifaddr *ifp,
unsigned long when)
{
if (!del_timer(&ifp->timer))
if (!timer_pending(&ifp->dad_timer))
in6_ifa_hold(ifp);
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);
mod_timer(&ifp->dad_timer, jiffies + when);
}
static int snmp6_alloc_dev(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(idev->mc_list != NULL);
WARN_ON(timer_pending(&idev->rs_timer));
#ifdef NET_REFCNT_DEBUG
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)
rwlock_init(&ndev->lock);
ndev->dev = dev;
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));
ndev->cnf.mtu6 = dev->mtu;
ndev->cnf.sysctl = NULL;
......@@ -776,7 +773,7 @@ void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp)
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);
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,
spin_lock_init(&ifa->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);
ifa->timer.data = (unsigned long) ifa;
ifa->scope = scope;
ifa->prefix_len = pfxlen;
ifa->flags = flags | IFA_F_TENTATIVE;
......@@ -994,7 +991,7 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp)
}
write_unlock_bh(&idev->lock);
addrconf_del_timer(ifp);
addrconf_del_dad_timer(ifp);
ipv6_ifa_notify(RTM_DELADDR, ifp);
......@@ -1617,7 +1614,7 @@ static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed)
{
if (ifp->flags&IFA_F_PERMANENT) {
spin_lock_bh(&ifp->lock);
addrconf_del_timer(ifp);
addrconf_del_dad_timer(ifp);
ifp->flags |= IFA_F_TENTATIVE;
if (dad_failed)
ifp->flags |= IFA_F_DADFAILED;
......@@ -3085,7 +3082,7 @@ static int addrconf_ifdown(struct net_device *dev, int how)
hlist_for_each_entry_rcu(ifa, h, addr_lst) {
if (ifa->idev == idev) {
hlist_del_init_rcu(&ifa->addr_lst);
addrconf_del_timer(ifa);
addrconf_del_dad_timer(ifa);
goto restart;
}
}
......@@ -3094,6 +3091,8 @@ static int addrconf_ifdown(struct net_device *dev, int how)
write_lock_bh(&idev->lock);
addrconf_del_rs_timer(idev);
/* Step 2: clear flags for stateless addrconf */
if (!how)
idev->if_flags &= ~(IF_RS_SENT|IF_RA_RCVD|IF_READY);
......@@ -3123,7 +3122,7 @@ static int addrconf_ifdown(struct net_device *dev, int how)
while (!list_empty(&idev->addr_list)) {
ifa = list_first_entry(&idev->addr_list,
struct inet6_ifaddr, if_list);
addrconf_del_timer(ifa);
addrconf_del_dad_timer(ifa);
list_del(&ifa->if_list);
......@@ -3165,10 +3164,10 @@ static int addrconf_ifdown(struct net_device *dev, int how)
static void addrconf_rs_timer(unsigned long data)
{
struct inet6_ifaddr *ifp = (struct inet6_ifaddr *) data;
struct inet6_dev *idev = ifp->idev;
struct inet6_dev *idev = (struct inet6_dev *)data;
struct in6_addr lladdr;
read_lock(&idev->lock);
write_lock(&idev->lock);
if (idev->dead || !(idev->if_flags & IF_READY))
goto out;
......@@ -3179,18 +3178,19 @@ static void addrconf_rs_timer(unsigned long data)
if (idev->if_flags & IF_RA_RCVD)
goto out;
spin_lock(&ifp->lock);
if (ifp->probes++ < idev->cnf.rtr_solicits) {
/* The wait after the last probe can be shorter */
addrconf_mod_timer(ifp, AC_RS,
(ifp->probes == idev->cnf.rtr_solicits) ?
idev->cnf.rtr_solicit_delay :
idev->cnf.rtr_solicit_interval);
spin_unlock(&ifp->lock);
if (idev->rs_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;
ndisc_send_rs(idev->dev, &ifp->addr, &in6addr_linklocal_allrouters);
/* The wait after the last probe can be shorter */
addrconf_mod_rs_timer(idev, (idev->rs_probes ==
idev->cnf.rtr_solicits) ?
idev->cnf.rtr_solicit_delay :
idev->cnf.rtr_solicit_interval);
} else {
spin_unlock(&ifp->lock);
/*
* Note: we do not support deprecated "all on-link"
* assumption any longer.
......@@ -3199,8 +3199,8 @@ static void addrconf_rs_timer(unsigned long data)
}
out:
read_unlock(&idev->lock);
in6_ifa_put(ifp);
write_unlock(&idev->lock);
in6_dev_put(idev);
}
/*
......@@ -3216,8 +3216,8 @@ static void addrconf_dad_kick(struct inet6_ifaddr *ifp)
else
rand_num = net_random() % (idev->cnf.rtr_solicit_delay ? : 1);
ifp->probes = idev->cnf.dad_transmits;
addrconf_mod_timer(ifp, AC_DAD, rand_num);
ifp->dad_probes = idev->cnf.dad_transmits;
addrconf_mod_dad_timer(ifp, rand_num);
}
static void addrconf_dad_start(struct inet6_ifaddr *ifp)
......@@ -3278,40 +3278,40 @@ static void addrconf_dad_timer(unsigned long data)
struct inet6_dev *idev = ifp->idev;
struct in6_addr mcaddr;
if (!ifp->probes && addrconf_dad_end(ifp))
if (!ifp->dad_probes && addrconf_dad_end(ifp))
goto out;
read_lock(&idev->lock);
write_lock(&idev->lock);
if (idev->dead || !(idev->if_flags & IF_READY)) {
read_unlock(&idev->lock);
write_unlock(&idev->lock);
goto out;
}
spin_lock(&ifp->lock);
if (ifp->state == INET6_IFADDR_STATE_DEAD) {
spin_unlock(&ifp->lock);
read_unlock(&idev->lock);
write_unlock(&idev->lock);
goto out;
}
if (ifp->probes == 0) {
if (ifp->dad_probes == 0) {
/*
* DAD was successful
*/
ifp->flags &= ~(IFA_F_TENTATIVE|IFA_F_OPTIMISTIC|IFA_F_DADFAILED);
spin_unlock(&ifp->lock);
read_unlock(&idev->lock);
write_unlock(&idev->lock);
addrconf_dad_completed(ifp);
goto out;
}
ifp->probes--;
addrconf_mod_timer(ifp, AC_DAD, ifp->idev->nd_parms->retrans_time);
ifp->dad_probes--;
addrconf_mod_dad_timer(ifp, ifp->idev->nd_parms->retrans_time);
spin_unlock(&ifp->lock);
read_unlock(&idev->lock);
write_unlock(&idev->lock);
/* send a neighbour solicitation for our addr */
addrconf_addr_solict_mult(&ifp->addr, &mcaddr);
......@@ -3323,6 +3323,9 @@ static void addrconf_dad_timer(unsigned long data)
static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
{
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.
......@@ -3343,13 +3346,20 @@ static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
* [...] as part of DAD [...] there is no need
* 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);
ifp->probes = 1;
write_lock_bh(&ifp->idev->lock);
spin_lock(&ifp->lock);
ifp->idev->rs_probes = 1;
ifp->idev->if_flags |= IF_RS_SENT;
addrconf_mod_timer(ifp, AC_RS, ifp->idev->cnf.rtr_solicit_interval);
spin_unlock_bh(&ifp->lock);
addrconf_mod_rs_timer(ifp->idev,
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