Skip to content

Commit

Permalink
ipv6: split duplicate address detection and router solicitation timer
Browse files Browse the repository at this point in the history
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: Hannes Frederic Sowa <hannes@stressinduktion.org>
Reviewed-by: Flavio Leitner <fbl@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
strssndktn authored and davem330 committed Jun 25, 2013
1 parent 51151a1 commit b7b1bfc
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 75 deletions.
8 changes: 6 additions & 2 deletions include/net/if_inet6.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ struct inet6_ifaddr {

int state;

__u8 probes;
__u8 dad_probes;
__u8 flags;

__u16 scope;

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;
Expand Down Expand Up @@ -195,6 +195,10 @@ struct inet6_dev {
struct neigh_parms *nd_parms;
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;
};
Expand Down
164 changes: 91 additions & 73 deletions net/ipv6/addrconf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -1447,6 +1444,23 @@ int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,
}
EXPORT_SYMBOL(ipv6_dev_get_saddr);

static int __ipv6_get_lladdr(struct inet6_dev *idev, struct in6_addr *addr,
unsigned char banned_flags)
{
struct inet6_ifaddr *ifp;
int err = -EADDRNOTAVAIL;

list_for_each_entry(ifp, &idev->addr_list, if_list) {
if (ifp->scope == IFA_LINK &&
!(ifp->flags & banned_flags)) {
*addr = ifp->addr;
err = 0;
break;
}
}
return err;
}

int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr,
unsigned char banned_flags)
{
Expand All @@ -1456,17 +1470,8 @@ int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr,
rcu_read_lock();
idev = __in6_dev_get(dev);
if (idev) {
struct inet6_ifaddr *ifp;

read_lock_bh(&idev->lock);
list_for_each_entry(ifp, &idev->addr_list, if_list) {
if (ifp->scope == IFA_LINK &&
!(ifp->flags & banned_flags)) {
*addr = ifp->addr;
err = 0;
break;
}
}
err = __ipv6_get_lladdr(idev, addr, banned_flags);
read_unlock_bh(&idev->lock);
}
rcu_read_unlock();
Expand Down Expand Up @@ -1580,7 +1585,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;
Expand Down Expand Up @@ -3036,7 +3041,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;
}
}
Expand All @@ -3045,6 +3050,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);
Expand Down Expand Up @@ -3074,7 +3081,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);

Expand Down Expand Up @@ -3116,10 +3123,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;

Expand All @@ -3130,18 +3137,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.
Expand All @@ -3150,8 +3158,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);
}

/*
Expand All @@ -3167,8 +3175,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)
Expand Down Expand Up @@ -3229,40 +3237,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);
Expand All @@ -3274,6 +3282,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.
Expand All @@ -3294,13 +3305,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);
}
}

Expand Down

0 comments on commit b7b1bfc

Please sign in to comment.