Commit adc176c5 authored by Erik Nordmark's avatar Erik Nordmark Committed by David S. Miller

ipv6 addrconf: Implemented enhanced DAD (RFC7527)

Implemented RFC7527 Enhanced DAD.
IPv6 duplicate address detection can fail if there is some temporary
loopback of Ethernet frames. RFC7527 solves this by including a random
nonce in the NS messages used for DAD, and if an NS is received with the
same nonce it is assumed to be a looped back DAD probe and is ignored.
RFC7527 is enabled by default. Can be disabled by setting both of
conf/{all,interface}/enhanced_dad to zero.
Signed-off-by: default avatarErik Nordmark <nordmark@arista.com>
Signed-off-by: default avatarBob Gilligan <gilligan@arista.com>
Reviewed-by: default avatarHannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent ce84c7c6
...@@ -1734,6 +1734,15 @@ drop_unsolicited_na - BOOLEAN ...@@ -1734,6 +1734,15 @@ drop_unsolicited_na - BOOLEAN
By default this is turned off. By default this is turned off.
enhanced_dad - BOOLEAN
Include a nonce option in the IPv6 neighbor solicitation messages used for
duplicate address detection per RFC7527. A received DAD NS will only signal
a duplicate address if the nonce is different. This avoids any false
detection of duplicates due to loopback of the NS messages that we send.
The nonce option will be sent on an interface unless both of
conf/{all,interface}/enhanced_dad are set to FALSE.
Default: TRUE
icmp/*: icmp/*:
ratelimit - INTEGER ratelimit - INTEGER
Limit the maximal rates for sending ICMPv6 packets. Limit the maximal rates for sending ICMPv6 packets.
......
...@@ -68,6 +68,7 @@ struct ipv6_devconf { ...@@ -68,6 +68,7 @@ struct ipv6_devconf {
#ifdef CONFIG_IPV6_SEG6_HMAC #ifdef CONFIG_IPV6_SEG6_HMAC
__s32 seg6_require_hmac; __s32 seg6_require_hmac;
#endif #endif
__u32 enhanced_dad;
struct ctl_table_header *sysctl_header; struct ctl_table_header *sysctl_header;
}; };
......
...@@ -55,6 +55,7 @@ struct inet6_ifaddr { ...@@ -55,6 +55,7 @@ struct inet6_ifaddr {
__u8 stable_privacy_retry; __u8 stable_privacy_retry;
__u16 scope; __u16 scope;
__u64 dad_nonce;
unsigned long cstamp; /* created timestamp */ unsigned long cstamp; /* created timestamp */
unsigned long tstamp; /* updated timestamp */ unsigned long tstamp; /* updated timestamp */
......
...@@ -31,6 +31,7 @@ enum { ...@@ -31,6 +31,7 @@ enum {
ND_OPT_PREFIX_INFO = 3, /* RFC2461 */ ND_OPT_PREFIX_INFO = 3, /* RFC2461 */
ND_OPT_REDIRECT_HDR = 4, /* RFC2461 */ ND_OPT_REDIRECT_HDR = 4, /* RFC2461 */
ND_OPT_MTU = 5, /* RFC2461 */ ND_OPT_MTU = 5, /* RFC2461 */
ND_OPT_NONCE = 14, /* RFC7527 */
__ND_OPT_ARRAY_MAX, __ND_OPT_ARRAY_MAX,
ND_OPT_ROUTE_INFO = 24, /* RFC4191 */ ND_OPT_ROUTE_INFO = 24, /* RFC4191 */
ND_OPT_RDNSS = 25, /* RFC5006 */ ND_OPT_RDNSS = 25, /* RFC5006 */
...@@ -121,6 +122,7 @@ struct ndisc_options { ...@@ -121,6 +122,7 @@ struct ndisc_options {
#define nd_opts_pi_end nd_opt_array[__ND_OPT_PREFIX_INFO_END] #define nd_opts_pi_end nd_opt_array[__ND_OPT_PREFIX_INFO_END]
#define nd_opts_rh nd_opt_array[ND_OPT_REDIRECT_HDR] #define nd_opts_rh nd_opt_array[ND_OPT_REDIRECT_HDR]
#define nd_opts_mtu nd_opt_array[ND_OPT_MTU] #define nd_opts_mtu nd_opt_array[ND_OPT_MTU]
#define nd_opts_nonce nd_opt_array[ND_OPT_NONCE]
#define nd_802154_opts_src_lladdr nd_802154_opt_array[ND_OPT_SOURCE_LL_ADDR] #define nd_802154_opts_src_lladdr nd_802154_opt_array[ND_OPT_SOURCE_LL_ADDR]
#define nd_802154_opts_tgt_lladdr nd_802154_opt_array[ND_OPT_TARGET_LL_ADDR] #define nd_802154_opts_tgt_lladdr nd_802154_opt_array[ND_OPT_TARGET_LL_ADDR]
...@@ -398,7 +400,8 @@ void ndisc_cleanup(void); ...@@ -398,7 +400,8 @@ void ndisc_cleanup(void);
int ndisc_rcv(struct sk_buff *skb); int ndisc_rcv(struct sk_buff *skb);
void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit, void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
const struct in6_addr *daddr, const struct in6_addr *saddr); const struct in6_addr *daddr, const struct in6_addr *saddr,
u64 nonce);
void ndisc_send_rs(struct net_device *dev, void ndisc_send_rs(struct net_device *dev,
const struct in6_addr *saddr, const struct in6_addr *daddr); const struct in6_addr *saddr, const struct in6_addr *daddr);
......
...@@ -181,6 +181,7 @@ enum { ...@@ -181,6 +181,7 @@ enum {
DEVCONF_RTR_SOLICIT_MAX_INTERVAL, DEVCONF_RTR_SOLICIT_MAX_INTERVAL,
DEVCONF_SEG6_ENABLED, DEVCONF_SEG6_ENABLED,
DEVCONF_SEG6_REQUIRE_HMAC, DEVCONF_SEG6_REQUIRE_HMAC,
DEVCONF_ENHANCED_DAD,
DEVCONF_MAX DEVCONF_MAX
}; };
......
...@@ -242,6 +242,7 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = { ...@@ -242,6 +242,7 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = {
#ifdef CONFIG_IPV6_SEG6_HMAC #ifdef CONFIG_IPV6_SEG6_HMAC
.seg6_require_hmac = 0, .seg6_require_hmac = 0,
#endif #endif
.enhanced_dad = 1,
}; };
static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = { static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
...@@ -292,6 +293,7 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = { ...@@ -292,6 +293,7 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
#ifdef CONFIG_IPV6_SEG6_HMAC #ifdef CONFIG_IPV6_SEG6_HMAC
.seg6_require_hmac = 0, .seg6_require_hmac = 0,
#endif #endif
.enhanced_dad = 1,
}; };
/* Check if a valid qdisc is available */ /* Check if a valid qdisc is available */
...@@ -3735,12 +3737,21 @@ static void addrconf_dad_kick(struct inet6_ifaddr *ifp) ...@@ -3735,12 +3737,21 @@ static void addrconf_dad_kick(struct inet6_ifaddr *ifp)
{ {
unsigned long rand_num; unsigned long rand_num;
struct inet6_dev *idev = ifp->idev; struct inet6_dev *idev = ifp->idev;
u64 nonce;
if (ifp->flags & IFA_F_OPTIMISTIC) if (ifp->flags & IFA_F_OPTIMISTIC)
rand_num = 0; rand_num = 0;
else else
rand_num = prandom_u32() % (idev->cnf.rtr_solicit_delay ? : 1); rand_num = prandom_u32() % (idev->cnf.rtr_solicit_delay ? : 1);
nonce = 0;
if (idev->cnf.enhanced_dad ||
dev_net(idev->dev)->ipv6.devconf_all->enhanced_dad) {
do
get_random_bytes(&nonce, 6);
while (nonce == 0);
}
ifp->dad_nonce = nonce;
ifp->dad_probes = idev->cnf.dad_transmits; ifp->dad_probes = idev->cnf.dad_transmits;
addrconf_mod_dad_work(ifp, rand_num); addrconf_mod_dad_work(ifp, rand_num);
} }
...@@ -3918,7 +3929,8 @@ static void addrconf_dad_work(struct work_struct *w) ...@@ -3918,7 +3929,8 @@ static void addrconf_dad_work(struct work_struct *w)
/* 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);
ndisc_send_ns(ifp->idev->dev, &ifp->addr, &mcaddr, &in6addr_any); ndisc_send_ns(ifp->idev->dev, &ifp->addr, &mcaddr, &in6addr_any,
ifp->dad_nonce);
out: out:
in6_ifa_put(ifp); in6_ifa_put(ifp);
rtnl_unlock(); rtnl_unlock();
...@@ -4962,6 +4974,7 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf, ...@@ -4962,6 +4974,7 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
#ifdef CONFIG_IPV6_SEG6_HMAC #ifdef CONFIG_IPV6_SEG6_HMAC
array[DEVCONF_SEG6_REQUIRE_HMAC] = cnf->seg6_require_hmac; array[DEVCONF_SEG6_REQUIRE_HMAC] = cnf->seg6_require_hmac;
#endif #endif
array[DEVCONF_ENHANCED_DAD] = cnf->enhanced_dad;
} }
static inline size_t inet6_ifla6_size(void) static inline size_t inet6_ifla6_size(void)
...@@ -6069,6 +6082,13 @@ static const struct ctl_table addrconf_sysctl[] = { ...@@ -6069,6 +6082,13 @@ static const struct ctl_table addrconf_sysctl[] = {
.proc_handler = proc_dointvec, .proc_handler = proc_dointvec,
}, },
#endif #endif
{
.procname = "enhanced_dad",
.data = &ipv6_devconf.enhanced_dad,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
{ {
/* sentinel */ /* sentinel */
} }
......
...@@ -233,6 +233,7 @@ struct ndisc_options *ndisc_parse_options(const struct net_device *dev, ...@@ -233,6 +233,7 @@ struct ndisc_options *ndisc_parse_options(const struct net_device *dev,
case ND_OPT_SOURCE_LL_ADDR: case ND_OPT_SOURCE_LL_ADDR:
case ND_OPT_TARGET_LL_ADDR: case ND_OPT_TARGET_LL_ADDR:
case ND_OPT_MTU: case ND_OPT_MTU:
case ND_OPT_NONCE:
case ND_OPT_REDIRECT_HDR: case ND_OPT_REDIRECT_HDR:
if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) { if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) {
ND_PRINTK(2, warn, ND_PRINTK(2, warn,
...@@ -568,7 +569,8 @@ static void ndisc_send_unsol_na(struct net_device *dev) ...@@ -568,7 +569,8 @@ static void ndisc_send_unsol_na(struct net_device *dev)
} }
void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit, void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
const struct in6_addr *daddr, const struct in6_addr *saddr) const struct in6_addr *daddr, const struct in6_addr *saddr,
u64 nonce)
{ {
struct sk_buff *skb; struct sk_buff *skb;
struct in6_addr addr_buf; struct in6_addr addr_buf;
...@@ -588,6 +590,8 @@ void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit, ...@@ -588,6 +590,8 @@ void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
if (inc_opt) if (inc_opt)
optlen += ndisc_opt_addr_space(dev, optlen += ndisc_opt_addr_space(dev,
NDISC_NEIGHBOUR_SOLICITATION); NDISC_NEIGHBOUR_SOLICITATION);
if (nonce != 0)
optlen += 8;
skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen); skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
if (!skb) if (!skb)
...@@ -605,6 +609,13 @@ void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit, ...@@ -605,6 +609,13 @@ void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
ndisc_fill_addr_option(skb, ND_OPT_SOURCE_LL_ADDR, ndisc_fill_addr_option(skb, ND_OPT_SOURCE_LL_ADDR,
dev->dev_addr, dev->dev_addr,
NDISC_NEIGHBOUR_SOLICITATION); NDISC_NEIGHBOUR_SOLICITATION);
if (nonce != 0) {
u8 *opt = skb_put(skb, 8);
opt[0] = ND_OPT_NONCE;
opt[1] = 8 >> 3;
memcpy(opt + 2, &nonce, 6);
}
ndisc_send_skb(skb, daddr, saddr); ndisc_send_skb(skb, daddr, saddr);
} }
...@@ -693,12 +704,12 @@ static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb) ...@@ -693,12 +704,12 @@ static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb)
"%s: trying to ucast probe in NUD_INVALID: %pI6\n", "%s: trying to ucast probe in NUD_INVALID: %pI6\n",
__func__, target); __func__, target);
} }
ndisc_send_ns(dev, target, target, saddr); ndisc_send_ns(dev, target, target, saddr, 0);
} else if ((probes -= NEIGH_VAR(neigh->parms, APP_PROBES)) < 0) { } else if ((probes -= NEIGH_VAR(neigh->parms, APP_PROBES)) < 0) {
neigh_app_ns(neigh); neigh_app_ns(neigh);
} else { } else {
addrconf_addr_solict_mult(target, &mcaddr); addrconf_addr_solict_mult(target, &mcaddr);
ndisc_send_ns(dev, target, &mcaddr, saddr); ndisc_send_ns(dev, target, &mcaddr, saddr, 0);
} }
} }
...@@ -742,6 +753,7 @@ static void ndisc_recv_ns(struct sk_buff *skb) ...@@ -742,6 +753,7 @@ static void ndisc_recv_ns(struct sk_buff *skb)
int dad = ipv6_addr_any(saddr); int dad = ipv6_addr_any(saddr);
bool inc; bool inc;
int is_router = -1; int is_router = -1;
u64 nonce = 0;
if (skb->len < sizeof(struct nd_msg)) { if (skb->len < sizeof(struct nd_msg)) {
ND_PRINTK(2, warn, "NS: packet too short\n"); ND_PRINTK(2, warn, "NS: packet too short\n");
...@@ -786,6 +798,8 @@ static void ndisc_recv_ns(struct sk_buff *skb) ...@@ -786,6 +798,8 @@ static void ndisc_recv_ns(struct sk_buff *skb)
return; return;
} }
} }
if (ndopts.nd_opts_nonce)
memcpy(&nonce, (u8 *)(ndopts.nd_opts_nonce + 1), 6);
inc = ipv6_addr_is_multicast(daddr); inc = ipv6_addr_is_multicast(daddr);
...@@ -794,6 +808,15 @@ static void ndisc_recv_ns(struct sk_buff *skb) ...@@ -794,6 +808,15 @@ static void ndisc_recv_ns(struct sk_buff *skb)
have_ifp: have_ifp:
if (ifp->flags & (IFA_F_TENTATIVE|IFA_F_OPTIMISTIC)) { if (ifp->flags & (IFA_F_TENTATIVE|IFA_F_OPTIMISTIC)) {
if (dad) { if (dad) {
if (nonce != 0 && ifp->dad_nonce == nonce) {
u8 *np = (u8 *)&nonce;
/* Matching nonce if looped back */
ND_PRINTK(2, notice,
"%s: IPv6 DAD loopback for address %pI6c nonce %pM ignored\n",
ifp->idev->dev->name,
&ifp->addr, np);
goto out;
}
/* /*
* We are colliding with another node * We are colliding with another node
* who is doing DAD * who is doing DAD
......
...@@ -527,7 +527,7 @@ static void rt6_probe_deferred(struct work_struct *w) ...@@ -527,7 +527,7 @@ static void rt6_probe_deferred(struct work_struct *w)
container_of(w, struct __rt6_probe_work, work); container_of(w, struct __rt6_probe_work, work);
addrconf_addr_solict_mult(&work->target, &mcaddr); addrconf_addr_solict_mult(&work->target, &mcaddr);
ndisc_send_ns(work->dev, &work->target, &mcaddr, NULL); ndisc_send_ns(work->dev, &work->target, &mcaddr, NULL, 0);
dev_put(work->dev); dev_put(work->dev);
kfree(work); kfree(work);
} }
......
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