From 9d4a0314642918cbda9ed4012df51e8df608fce6 Mon Sep 17 00:00:00 2001 From: Hannes Frederic Sowa Date: Fri, 26 Jul 2013 17:05:16 +0200 Subject: ipv4, ipv6: send igmpv3/mld packets with TC_PRIO_CONTROL v2: a) Also send ipv4 igmp messages with TC_PRIO_CONTROL Cc: William Manley Cc: Lukas Tribus Acked-by: Benjamin LaHaise Signed-off-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 99cd65c715cd..db25b8eb62bd 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -1376,6 +1377,7 @@ static struct sk_buff *mld_newpack(struct inet6_dev *idev, int size) if (!skb) return NULL; + skb->priority = TC_PRIO_CONTROL; skb_reserve(skb, hlen); if (__ipv6_get_lladdr(idev, &addr_buf, IFA_F_TENTATIVE)) { @@ -1769,7 +1771,7 @@ static void igmp6_send(struct in6_addr *addr, struct net_device *dev, int type) rcu_read_unlock(); return; } - + skb->priority = TC_PRIO_CONTROL; skb_reserve(skb, hlen); if (ipv6_get_lladdr(dev, &addr_buf, IFA_F_TENTATIVE)) { -- cgit v1.2.3 From fc4eba58b4c1462ff3d6247b66fb47d6928db6d2 Mon Sep 17 00:00:00 2001 From: Hannes Frederic Sowa Date: Wed, 14 Aug 2013 01:03:46 +0200 Subject: ipv6: make unsolicited report intervals configurable for mld Commit cab70040dfd95ee32144f02fade64f0cb94f31a0 ("net: igmp: Reduce Unsolicited report interval to 1s when using IGMPv3") and 2690048c01f32bf45d1c1e1ab3079bc10ad2aea7 ("net: igmp: Allow user-space configuration of igmp unsolicited report interval") by William Manley made igmp unsolicited report intervals configurable per interface and corrected the interval of unsolicited igmpv3 report messages resendings to 1s. Same needs to be done for IPv6: MLDv1 (RFC2710 7.10.): 10 seconds MLDv2 (RFC3810 9.11.): 1 second Both intervals are configurable via new procfs knobs mldv1_unsolicited_report_interval and mldv2_unsolicited_report_interval. (also added .force_mld_version to ipv6_devconf_dflt to bring structs in line without semantic changes) v2: a) Joined documentation update for IPv4 and IPv6 MLD/IGMP unsolicited_report_interval procfs knobs. b) incorporate stylistic feedback from William Manley v3: a) add new DEVCONF_* values to the end of the enum (thanks to David Miller) Cc: Cong Wang Cc: William Manley Cc: Benjamin LaHaise Cc: YOSHIFUJI Hideaki Signed-off-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index db25b8eb62bd..6c76df9909bf 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -108,7 +108,6 @@ static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, struct inet6_dev *idev); -#define IGMP6_UNSOLICITED_IVAL (10*HZ) #define MLD_QRV_DEFAULT 2 #define MLD_V1_SEEN(idev) (dev_net((idev)->dev)->ipv6.devconf_all->force_mld_version == 1 || \ @@ -129,6 +128,18 @@ int sysctl_mld_max_msf __read_mostly = IPV6_MLD_MAX_MSF; pmc != NULL; \ pmc = rcu_dereference(pmc->next)) +static int unsolicited_report_interval(struct inet6_dev *idev) +{ + int iv; + + if (MLD_V1_SEEN(idev)) + iv = idev->cnf.mldv1_unsolicited_report_interval; + else + iv = idev->cnf.mldv2_unsolicited_report_interval; + + return iv > 0 ? iv : 1; +} + int ipv6_sock_mc_join(struct sock *sk, int ifindex, const struct in6_addr *addr) { struct net_device *dev = NULL; @@ -2158,7 +2169,7 @@ static void igmp6_join_group(struct ifmcaddr6 *ma) igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REPORT); - delay = net_random() % IGMP6_UNSOLICITED_IVAL; + delay = net_random() % unsolicited_report_interval(ma->idev); spin_lock_bh(&ma->mca_lock); if (del_timer(&ma->mca_timer)) { @@ -2325,7 +2336,7 @@ void ipv6_mc_init_dev(struct inet6_dev *idev) setup_timer(&idev->mc_dad_timer, mld_dad_timer_expire, (unsigned long)idev); idev->mc_qrv = MLD_QRV_DEFAULT; - idev->mc_maxdelay = IGMP6_UNSOLICITED_IVAL; + idev->mc_maxdelay = unsolicited_report_interval(idev); idev->mc_v1_seen = 0; write_unlock_bh(&idev->lock); } -- cgit v1.2.3 From 846989635b368c41efd9079ec6fe0e8d2845ec11 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Tue, 20 Aug 2013 12:22:00 +0200 Subject: net: ipv6: igmp6_event_query: use msecs_to_jiffies Use proper API functions to calculate jiffies from milliseconds and not the crude method of dividing HZ by a value. This ensures more accurate values even in the case of strange HZ values. While at it, also simplify code in the mlh2 case by using max(). Signed-off-by: Daniel Borkmann Acked-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 6c76df9909bf..57863e206d56 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -1150,9 +1150,7 @@ int igmp6_event_query(struct sk_buff *skb) int switchback; /* MLDv1 router present */ - /* Translate milliseconds to jiffies */ - max_delay = (ntohs(mld->mld_maxdelay)*HZ)/1000; - + max_delay = msecs_to_jiffies(ntohs(mld->mld_maxdelay)); switchback = (idev->mc_qrv + 1) * max_delay; idev->mc_v1_seen = jiffies + switchback; @@ -1169,10 +1167,11 @@ int igmp6_event_query(struct sk_buff *skb) return -EINVAL; mlh2 = (struct mld2_query *)skb_transport_header(skb); - max_delay = (MLDV2_MRC(ntohs(mlh2->mld2q_mrc))*HZ)/1000; - if (!max_delay) - max_delay = 1; + + max_delay = max(msecs_to_jiffies(MLDV2_MRC(ntohs(mlh2->mld2q_mrc))), 1UL); + idev->mc_maxdelay = max_delay; + if (mlh2->mld2q_qrv) idev->mc_qrv = mlh2->mld2q_qrv; if (group_type == IPV6_ADDR_ANY) { /* general query */ -- cgit v1.2.3 From c2cef4e88808cc566956605a39a96080912c0566 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Tue, 20 Aug 2013 12:22:01 +0200 Subject: net: ipv6: minor: *_start_timer: rather use unsigned long For the functions mld_gq_start_timer(), mld_ifc_start_timer(), and mld_dad_start_timer(), rather use unsigned long than int as we operate only on unsigned values anyway. This seems more appropriate as there is no good reason to do type conversions to int, that could lead to future errors. Signed-off-by: Daniel Borkmann Acked-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 57863e206d56..33f98d9ac376 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -996,24 +996,24 @@ bool ipv6_chk_mcast_addr(struct net_device *dev, const struct in6_addr *group, static void mld_gq_start_timer(struct inet6_dev *idev) { - int tv = net_random() % idev->mc_maxdelay; + unsigned long tv = net_random() % idev->mc_maxdelay; idev->mc_gq_running = 1; if (!mod_timer(&idev->mc_gq_timer, jiffies+tv+2)) in6_dev_hold(idev); } -static void mld_ifc_start_timer(struct inet6_dev *idev, int delay) +static void mld_ifc_start_timer(struct inet6_dev *idev, unsigned long delay) { - int tv = net_random() % delay; + unsigned long tv = net_random() % delay; if (!mod_timer(&idev->mc_ifc_timer, jiffies+tv+2)) in6_dev_hold(idev); } -static void mld_dad_start_timer(struct inet6_dev *idev, int delay) +static void mld_dad_start_timer(struct inet6_dev *idev, unsigned long delay) { - int tv = net_random() % delay; + unsigned long tv = net_random() % delay; if (!mod_timer(&idev->mc_dad_timer, jiffies+tv+2)) in6_dev_hold(idev); -- cgit v1.2.3 From 9fd0784164047583fa856c9a5aeda1d6c6fb0399 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Tue, 20 Aug 2013 12:22:02 +0200 Subject: net: ipv6: mcast: minor: use defines for rfc3810/8.1 lengths Instead of hard-coding length values, use a define to make it clear where those lengths come from. Signed-off-by: Daniel Borkmann Acked-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 33f98d9ac376..98ead2b1a669 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -107,9 +107,12 @@ static int ip6_mc_add_src(struct inet6_dev *idev, const struct in6_addr *pmca, static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, struct inet6_dev *idev); - #define MLD_QRV_DEFAULT 2 +/* RFC3810, 8.1 Query Version Distinctions */ +#define MLD_V1_QUERY_LEN 24 +#define MLD_V2_QUERY_LEN_MIN 28 + #define MLD_V1_SEEN(idev) (dev_net((idev)->dev)->ipv6.devconf_all->force_mld_version == 1 || \ (idev)->cnf.force_mld_version == 1 || \ ((idev)->mc_v1_seen && \ @@ -1146,7 +1149,7 @@ int igmp6_event_query(struct sk_buff *skb) !(group_type&IPV6_ADDR_MULTICAST)) return -EINVAL; - if (len == 24) { + if (len == MLD_V1_QUERY_LEN) { int switchback; /* MLDv1 router present */ @@ -1160,7 +1163,7 @@ int igmp6_event_query(struct sk_buff *skb) __in6_dev_put(idev); /* clear deleted report items */ mld_clear_delrec(idev); - } else if (len >= 28) { + } else if (len >= MLD_V2_QUERY_LEN_MIN) { int srcs_offset = sizeof(struct mld2_query) - sizeof(struct icmp6hdr); if (!pskb_may_pull(skb, srcs_offset)) -- cgit v1.2.3 From 89225d1ce6af3916bf32aecbe9d83f571a098588 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Wed, 4 Sep 2013 00:19:37 +0200 Subject: net: ipv6: mld: fix v1/v2 switchback timeout to rfc3810, 9.12. i) RFC3810, 9.2. Query Interval [QI] says: The Query Interval variable denotes the interval between General Queries sent by the Querier. Default value: 125 seconds. [...] ii) RFC3810, 9.3. Query Response Interval [QRI] says: The Maximum Response Delay used to calculate the Maximum Response Code inserted into the periodic General Queries. Default value: 10000 (10 seconds) [...] The number of seconds represented by the [Query Response Interval] must be less than the [Query Interval]. iii) RFC3810, 9.12. Older Version Querier Present Timeout [OVQPT] says: The Older Version Querier Present Timeout is the time-out for transitioning a host back to MLDv2 Host Compatibility Mode. When an MLDv1 query is received, MLDv2 hosts set their Older Version Querier Present Timer to [Older Version Querier Present Timeout]. This value MUST be ([Robustness Variable] times (the [Query Interval] in the last Query received)) plus ([Query Response Interval]). Hence, on *default* the timeout results in: [RV] = 2, [QI] = 125sec, [QRI] = 10sec [OVQPT] = [RV] * [QI] + [QRI] = 260sec Having that said, we currently calculate [OVQPT] (here given as 'switchback' variable) as ... switchback = (idev->mc_qrv + 1) * max_delay RFC3810, 9.12. says "the [Query Interval] in the last Query received". In section "9.14. Configuring timers", it is said: This section is meant to provide advice to network administrators on how to tune these settings to their network. Ambitious router implementations might tune these settings dynamically based upon changing characteristics of the network. [...] iv) RFC38010, 9.14.2. Query Interval: The overall level of periodic MLD traffic is inversely proportional to the Query Interval. A longer Query Interval results in a lower overall level of MLD traffic. The value of the Query Interval MUST be equal to or greater than the Maximum Response Delay used to calculate the Maximum Response Code inserted in General Query messages. I assume that was why switchback is calculated as is (3 * max_delay), although this setting seems to be meant for routers only to configure their [QI] interval for non-default intervals. So usage here like this is clearly wrong. Concluding, the current behaviour in IPv6's multicast code is not conform to the RFC as switch back is calculated wrongly. That is, it has a too small value, so MLDv2 hosts switch back again to MLDv2 way too early, i.e. ~30secs instead of ~260secs on default. Hence, introduce necessary helper functions and fix this up properly as it should be. Introduced in 06da92283 ("[IPV6]: Add MLDv2 support."). Credits to Hannes Frederic Sowa who also had a hand in this as well. Also thanks to Hangbin Liu who did initial testing. Signed-off-by: Daniel Borkmann Cc: David Stevens Cc: Hannes Frederic Sowa Acked-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 6 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 98ead2b1a669..8992ff262789 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -108,6 +108,10 @@ static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, struct inet6_dev *idev); #define MLD_QRV_DEFAULT 2 +/* RFC3810, 9.2. Query Interval */ +#define MLD_QI_DEFAULT (125 * HZ) +/* RFC3810, 9.3. Query Response Interval */ +#define MLD_QRI_DEFAULT (10 * HZ) /* RFC3810, 8.1 Query Version Distinctions */ #define MLD_V1_QUERY_LEN 24 @@ -1112,6 +1116,93 @@ static bool mld_marksources(struct ifmcaddr6 *pmc, int nsrcs, return true; } +static void mld_set_v1_mode(struct inet6_dev *idev) +{ + /* RFC3810, relevant sections: + * - 9.1. Robustness Variable + * - 9.2. Query Interval + * - 9.3. Query Response Interval + * - 9.12. Older Version Querier Present Timeout + */ + unsigned long switchback; + + switchback = (idev->mc_qrv * idev->mc_qi) + idev->mc_qri; + + idev->mc_v1_seen = jiffies + switchback; +} + +static void mld_update_qrv(struct inet6_dev *idev, + const struct mld2_query *mlh2) +{ + /* RFC3810, relevant sections: + * - 5.1.8. QRV (Querier's Robustness Variable) + * - 9.1. Robustness Variable + */ + + /* The value of the Robustness Variable MUST NOT be zero, + * and SHOULD NOT be one. Catch this here if we ever run + * into such a case in future. + */ + WARN_ON(idev->mc_qrv == 0); + + if (mlh2->mld2q_qrv > 0) + idev->mc_qrv = mlh2->mld2q_qrv; + + if (unlikely(idev->mc_qrv < 2)) { + net_warn_ratelimited("IPv6: MLD: clamping QRV from %u to %u!\n", + idev->mc_qrv, MLD_QRV_DEFAULT); + idev->mc_qrv = MLD_QRV_DEFAULT; + } +} + +static void mld_update_qi(struct inet6_dev *idev, + const struct mld2_query *mlh2) +{ + /* RFC3810, relevant sections: + * - 5.1.9. QQIC (Querier's Query Interval Code) + * - 9.2. Query Interval + * - 9.12. Older Version Querier Present Timeout + * (the [Query Interval] in the last Query received) + */ + unsigned long mc_qqi; + + if (mlh2->mld2q_qqic < 128) { + mc_qqi = mlh2->mld2q_qqic; + } else { + unsigned long mc_man, mc_exp; + + mc_exp = MLDV2_QQIC_EXP(mlh2->mld2q_qqic); + mc_man = MLDV2_QQIC_MAN(mlh2->mld2q_qqic); + + mc_qqi = (mc_man | 0x10) << (mc_exp + 3); + } + + idev->mc_qi = mc_qqi * HZ; +} + +static void mld_update_qri(struct inet6_dev *idev, + const struct mld2_query *mlh2) +{ + /* RFC3810, relevant sections: + * - 5.1.3. Maximum Response Code + * - 9.3. Query Response Interval + */ + unsigned long mc_qri, mc_mrc = ntohs(mlh2->mld2q_mrc); + + if (mc_mrc < 32768) { + mc_qri = mc_mrc; + } else { + unsigned long mc_man, mc_exp; + + mc_exp = MLDV2_MRC_EXP(mc_mrc); + mc_man = MLDV2_MRC_MAN(mc_mrc); + + mc_qri = (mc_man | 0x1000) << (mc_exp + 3); + } + + idev->mc_qri = msecs_to_jiffies(mc_qri); +} + /* called with rcu_read_lock() */ int igmp6_event_query(struct sk_buff *skb) { @@ -1150,12 +1241,15 @@ int igmp6_event_query(struct sk_buff *skb) return -EINVAL; if (len == MLD_V1_QUERY_LEN) { - int switchback; /* MLDv1 router present */ - max_delay = msecs_to_jiffies(ntohs(mld->mld_maxdelay)); - switchback = (idev->mc_qrv + 1) * max_delay; - idev->mc_v1_seen = jiffies + switchback; + + mld_set_v1_mode(idev); + + /* cancel MLDv2 report timer */ + idev->mc_gq_running = 0; + if (del_timer(&idev->mc_gq_timer)) + __in6_dev_put(idev); /* cancel the interface change timer */ idev->mc_ifc_count = 0; @@ -1166,6 +1260,10 @@ int igmp6_event_query(struct sk_buff *skb) } else if (len >= MLD_V2_QUERY_LEN_MIN) { int srcs_offset = sizeof(struct mld2_query) - sizeof(struct icmp6hdr); + + /* hosts need to stay in MLDv1 mode, discard MLDv2 queries */ + if (MLD_V1_SEEN(idev)) + return 0; if (!pskb_may_pull(skb, srcs_offset)) return -EINVAL; @@ -1175,8 +1273,10 @@ int igmp6_event_query(struct sk_buff *skb) idev->mc_maxdelay = max_delay; - if (mlh2->mld2q_qrv) - idev->mc_qrv = mlh2->mld2q_qrv; + mld_update_qrv(idev, mlh2); + mld_update_qi(idev, mlh2); + mld_update_qri(idev, mlh2); + if (group_type == IPV6_ADDR_ANY) { /* general query */ if (mlh2->mld2q_nsrcs) return -EINVAL; /* no sources allowed */ @@ -2337,7 +2437,11 @@ void ipv6_mc_init_dev(struct inet6_dev *idev) (unsigned long)idev); setup_timer(&idev->mc_dad_timer, mld_dad_timer_expire, (unsigned long)idev); + idev->mc_qrv = MLD_QRV_DEFAULT; + idev->mc_qi = MLD_QI_DEFAULT; + idev->mc_qri = MLD_QRI_DEFAULT; + idev->mc_maxdelay = unsolicited_report_interval(idev); idev->mc_v1_seen = 0; write_unlock_bh(&idev->lock); -- cgit v1.2.3 From 6c567b78c8a7da26e5e6f5bd458882ad9967233a Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Wed, 4 Sep 2013 00:19:38 +0200 Subject: net: ipv6: mld: clean up MLD_V1_SEEN macro Replace the macro with a function to make it more readable. GCC will eventually decide whether to inline this or not (also, that's not fast-path anyway). Signed-off-by: Daniel Borkmann Cc: Hannes Frederic Sowa Acked-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 8992ff262789..f86e26b7296c 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -95,6 +95,7 @@ static void mld_ifc_event(struct inet6_dev *idev); static void mld_add_delrec(struct inet6_dev *idev, struct ifmcaddr6 *pmc); static void mld_del_delrec(struct inet6_dev *idev, const struct in6_addr *addr); static void mld_clear_delrec(struct inet6_dev *idev); +static bool mld_in_v1_mode(const struct inet6_dev *idev); static int sf_setstate(struct ifmcaddr6 *pmc); static void sf_markstate(struct ifmcaddr6 *pmc); static void ip6_mc_clear_src(struct ifmcaddr6 *pmc); @@ -117,11 +118,6 @@ static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, #define MLD_V1_QUERY_LEN 24 #define MLD_V2_QUERY_LEN_MIN 28 -#define MLD_V1_SEEN(idev) (dev_net((idev)->dev)->ipv6.devconf_all->force_mld_version == 1 || \ - (idev)->cnf.force_mld_version == 1 || \ - ((idev)->mc_v1_seen && \ - time_before(jiffies, (idev)->mc_v1_seen))) - #define IPV6_MLD_MAX_MSF 64 int sysctl_mld_max_msf __read_mostly = IPV6_MLD_MAX_MSF; @@ -139,7 +135,7 @@ static int unsolicited_report_interval(struct inet6_dev *idev) { int iv; - if (MLD_V1_SEEN(idev)) + if (mld_in_v1_mode(idev)) iv = idev->cnf.mldv1_unsolicited_report_interval; else iv = idev->cnf.mldv2_unsolicited_report_interval; @@ -695,7 +691,7 @@ static void igmp6_group_added(struct ifmcaddr6 *mc) if (!(dev->flags & IFF_UP) || (mc->mca_flags & MAF_NOREPORT)) return; - if (MLD_V1_SEEN(mc->idev)) { + if (mld_in_v1_mode(mc->idev)) { igmp6_join_group(mc); return; } @@ -1116,6 +1112,18 @@ static bool mld_marksources(struct ifmcaddr6 *pmc, int nsrcs, return true; } +static bool mld_in_v1_mode(const struct inet6_dev *idev) +{ + if (dev_net(idev->dev)->ipv6.devconf_all->force_mld_version == 1) + return true; + if (idev->cnf.force_mld_version == 1) + return true; + if (idev->mc_v1_seen && time_before(jiffies, idev->mc_v1_seen)) + return true; + + return false; +} + static void mld_set_v1_mode(struct inet6_dev *idev) { /* RFC3810, relevant sections: @@ -1262,7 +1270,7 @@ int igmp6_event_query(struct sk_buff *skb) sizeof(struct icmp6hdr); /* hosts need to stay in MLDv1 mode, discard MLDv2 queries */ - if (MLD_V1_SEEN(idev)) + if (mld_in_v1_mode(idev)) return 0; if (!pskb_may_pull(skb, srcs_offset)) return -EINVAL; @@ -1942,7 +1950,7 @@ err_out: static void mld_resend_report(struct inet6_dev *idev) { - if (MLD_V1_SEEN(idev)) { + if (mld_in_v1_mode(idev)) { struct ifmcaddr6 *mcaddr; read_lock_bh(&idev->lock); for (mcaddr = idev->mc_list; mcaddr; mcaddr = mcaddr->next) { @@ -2006,7 +2014,7 @@ static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode, else pmc->mca_sources = psf->sf_next; if (psf->sf_oldin && !(pmc->mca_flags & MAF_NOREPORT) && - !MLD_V1_SEEN(idev)) { + !mld_in_v1_mode(idev)) { psf->sf_crcount = idev->mc_qrv; psf->sf_next = pmc->mca_tomb; pmc->mca_tomb = psf; @@ -2306,7 +2314,7 @@ static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, static void igmp6_leave_group(struct ifmcaddr6 *ma) { - if (MLD_V1_SEEN(ma->idev)) { + if (mld_in_v1_mode(ma->idev)) { if (ma->mca_flags & MAF_LAST_REPORTER) igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REDUCTION); @@ -2340,7 +2348,7 @@ static void mld_ifc_timer_expire(unsigned long data) static void mld_ifc_event(struct inet6_dev *idev) { - if (MLD_V1_SEEN(idev)) + if (mld_in_v1_mode(idev)) return; idev->mc_ifc_count = idev->mc_qrv; mld_ifc_start_timer(idev, 1); @@ -2351,7 +2359,7 @@ static void igmp6_timer_handler(unsigned long data) { struct ifmcaddr6 *ma = (struct ifmcaddr6 *) data; - if (MLD_V1_SEEN(ma->idev)) + if (mld_in_v1_mode(ma->idev)) igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REPORT); else mld_send_report(ma->idev, ma); -- cgit v1.2.3 From e3f5b17047dec4acd8957dad053e70d87f18d97e Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Wed, 4 Sep 2013 00:19:39 +0200 Subject: net: ipv6: mld: get rid of MLDV2_MRC and simplify calculation Get rid of MLDV2_MRC and use our new macros for mantisse and exponent to calculate Maximum Response Delay out of the Maximum Response Code. Signed-off-by: Daniel Borkmann Cc: Hannes Frederic Sowa Acked-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index f86e26b7296c..005b22fd6bcb 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -1195,20 +1195,7 @@ static void mld_update_qri(struct inet6_dev *idev, * - 5.1.3. Maximum Response Code * - 9.3. Query Response Interval */ - unsigned long mc_qri, mc_mrc = ntohs(mlh2->mld2q_mrc); - - if (mc_mrc < 32768) { - mc_qri = mc_mrc; - } else { - unsigned long mc_man, mc_exp; - - mc_exp = MLDV2_MRC_EXP(mc_mrc); - mc_man = MLDV2_MRC_MAN(mc_mrc); - - mc_qri = (mc_man | 0x1000) << (mc_exp + 3); - } - - idev->mc_qri = msecs_to_jiffies(mc_qri); + idev->mc_qri = msecs_to_jiffies(mldv2_mrc(mlh2)); } /* called with rcu_read_lock() */ @@ -1277,8 +1264,7 @@ int igmp6_event_query(struct sk_buff *skb) mlh2 = (struct mld2_query *)skb_transport_header(skb); - max_delay = max(msecs_to_jiffies(MLDV2_MRC(ntohs(mlh2->mld2q_mrc))), 1UL); - + max_delay = max(msecs_to_jiffies(mldv2_mrc(mlh2)), 1UL); idev->mc_maxdelay = max_delay; mld_update_qrv(idev, mlh2); -- cgit v1.2.3 From 58c0ecfd8d9871cfa35bcdbf3e7b3ee9ca62ea67 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Wed, 4 Sep 2013 00:19:40 +0200 Subject: net: ipv6: mld: implement RFC3810 MLDv2 mode only RFC3810, 10. Security Considerations says under subsection 10.1. Query Message: A forged Version 1 Query message will put MLDv2 listeners on that link in MLDv1 Host Compatibility Mode. This scenario can be avoided by providing MLDv2 hosts with a configuration option to ignore Version 1 messages completely. Hence, implement a MLDv2-only mode that will ignore MLDv1 traffic: echo 2 > /proc/sys/net/ipv6/conf/ethX/force_mld_version or echo 2 > /proc/sys/net/ipv6/conf/all/force_mld_version Note that device has a higher precedence as it was previously also the case in the macro MLD_V1_SEEN() that would "short-circuit" if condition on case. Signed-off-by: Daniel Borkmann Cc: Hannes Frederic Sowa Acked-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 005b22fd6bcb..c916568d838a 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -1112,11 +1112,34 @@ static bool mld_marksources(struct ifmcaddr6 *pmc, int nsrcs, return true; } +static int mld_force_mld_version(const struct inet6_dev *idev) +{ + /* Normally, both are 0 here. If enforcement to a particular is + * being used, individual device enforcement will have a lower + * precedence over 'all' device (.../conf/all/force_mld_version). + */ + + if (dev_net(idev->dev)->ipv6.devconf_all->force_mld_version != 0) + return dev_net(idev->dev)->ipv6.devconf_all->force_mld_version; + else + return idev->cnf.force_mld_version; +} + +static bool mld_in_v2_mode_only(const struct inet6_dev *idev) +{ + return mld_force_mld_version(idev) == 2; +} + +static bool mld_in_v1_mode_only(const struct inet6_dev *idev) +{ + return mld_force_mld_version(idev) == 1; +} + static bool mld_in_v1_mode(const struct inet6_dev *idev) { - if (dev_net(idev->dev)->ipv6.devconf_all->force_mld_version == 1) - return true; - if (idev->cnf.force_mld_version == 1) + if (mld_in_v2_mode_only(idev)) + return false; + if (mld_in_v1_mode_only(idev)) return true; if (idev->mc_v1_seen && time_before(jiffies, idev->mc_v1_seen)) return true; @@ -1223,7 +1246,6 @@ int igmp6_event_query(struct sk_buff *skb) return -EINVAL; idev = __in6_dev_get(skb->dev); - if (idev == NULL) return 0; @@ -1236,6 +1258,10 @@ int igmp6_event_query(struct sk_buff *skb) return -EINVAL; if (len == MLD_V1_QUERY_LEN) { + /* Ignore v1 queries */ + if (mld_in_v2_mode_only(idev)) + return 0; + /* MLDv1 router present */ max_delay = msecs_to_jiffies(ntohs(mld->mld_maxdelay)); -- cgit v1.2.3 From cc7f7ab758f66a9110c8e737c3de3e9f5fc209b5 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Wed, 4 Sep 2013 00:19:41 +0200 Subject: net: ipv6: mld: similarly to MLDv2 have min max_delay of 1 Similarly as we do in MLDv2 queries, set a forged MLDv1 query with 0 ms mld_maxdelay to minimum timer shot time of 1 jiffies. This is eventually done in igmp6_group_queried() anyway, so we can simplify a check there. Signed-off-by: Daniel Borkmann Cc: Hannes Frederic Sowa Acked-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index c916568d838a..beb76b7989a7 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -1040,12 +1040,9 @@ static void igmp6_group_queried(struct ifmcaddr6 *ma, unsigned long resptime) delay = ma->mca_timer.expires - jiffies; } - if (delay >= resptime) { - if (resptime) - delay = net_random() % resptime; - else - delay = 1; - } + if (delay >= resptime) + delay = net_random() % resptime; + ma->mca_timer.expires = jiffies + delay; if (!mod_timer(&ma->mca_timer, jiffies + delay)) atomic_inc(&ma->mca_refcnt); @@ -1258,12 +1255,15 @@ int igmp6_event_query(struct sk_buff *skb) return -EINVAL; if (len == MLD_V1_QUERY_LEN) { + unsigned long mldv1_md; + /* Ignore v1 queries */ if (mld_in_v2_mode_only(idev)) return 0; /* MLDv1 router present */ - max_delay = msecs_to_jiffies(ntohs(mld->mld_maxdelay)); + mldv1_md = ntohs(mld->mld_maxdelay); + max_delay = max(msecs_to_jiffies(mldv1_md), 1UL); mld_set_v1_mode(idev); -- cgit v1.2.3 From 2b7c121f82b47ba5efac1ef47355376d1f8d6980 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Wed, 4 Sep 2013 00:19:42 +0200 Subject: net: ipv6: mld: refactor query processing into v1/v2 functions Make igmp6_event_query() a bit easier to read by refactoring code parts into mld_process_v1() and mld_process_v2(). Signed-off-by: Daniel Borkmann Cc: Hannes Frederic Sowa Acked-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 89 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 33 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index beb76b7989a7..04399cb71e14 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -1218,6 +1218,55 @@ static void mld_update_qri(struct inet6_dev *idev, idev->mc_qri = msecs_to_jiffies(mldv2_mrc(mlh2)); } +static int mld_process_v1(struct inet6_dev *idev, struct mld_msg *mld, + unsigned long *max_delay) +{ + unsigned long mldv1_md; + + /* Ignore v1 queries */ + if (mld_in_v2_mode_only(idev)) + return -EINVAL; + + /* MLDv1 router present */ + mldv1_md = ntohs(mld->mld_maxdelay); + *max_delay = max(msecs_to_jiffies(mldv1_md), 1UL); + + mld_set_v1_mode(idev); + + /* cancel MLDv2 report timer */ + idev->mc_gq_running = 0; + if (del_timer(&idev->mc_gq_timer)) + __in6_dev_put(idev); + + /* cancel the interface change timer */ + idev->mc_ifc_count = 0; + if (del_timer(&idev->mc_ifc_timer)) + __in6_dev_put(idev); + + /* clear deleted report items */ + mld_clear_delrec(idev); + + return 0; +} + +static int mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld, + unsigned long *max_delay) +{ + /* hosts need to stay in MLDv1 mode, discard MLDv2 queries */ + if (mld_in_v1_mode(idev)) + return -EINVAL; + + *max_delay = max(msecs_to_jiffies(mldv2_mrc(mld)), 1UL); + + mld_update_qrv(idev, mld); + mld_update_qi(idev, mld); + mld_update_qri(idev, mld); + + idev->mc_maxdelay = *max_delay; + + return 0; +} + /* called with rcu_read_lock() */ int igmp6_event_query(struct sk_buff *skb) { @@ -1229,7 +1278,7 @@ int igmp6_event_query(struct sk_buff *skb) struct mld_msg *mld; int group_type; int mark = 0; - int len; + int len, err; if (!pskb_may_pull(skb, sizeof(struct in6_addr))) return -EINVAL; @@ -1255,47 +1304,21 @@ int igmp6_event_query(struct sk_buff *skb) return -EINVAL; if (len == MLD_V1_QUERY_LEN) { - unsigned long mldv1_md; - - /* Ignore v1 queries */ - if (mld_in_v2_mode_only(idev)) - return 0; - - /* MLDv1 router present */ - mldv1_md = ntohs(mld->mld_maxdelay); - max_delay = max(msecs_to_jiffies(mldv1_md), 1UL); - - mld_set_v1_mode(idev); - - /* cancel MLDv2 report timer */ - idev->mc_gq_running = 0; - if (del_timer(&idev->mc_gq_timer)) - __in6_dev_put(idev); - - /* cancel the interface change timer */ - idev->mc_ifc_count = 0; - if (del_timer(&idev->mc_ifc_timer)) - __in6_dev_put(idev); - /* clear deleted report items */ - mld_clear_delrec(idev); + err = mld_process_v1(idev, mld, &max_delay); + if (err < 0) + return err; } else if (len >= MLD_V2_QUERY_LEN_MIN) { int srcs_offset = sizeof(struct mld2_query) - sizeof(struct icmp6hdr); - /* hosts need to stay in MLDv1 mode, discard MLDv2 queries */ - if (mld_in_v1_mode(idev)) - return 0; if (!pskb_may_pull(skb, srcs_offset)) return -EINVAL; mlh2 = (struct mld2_query *)skb_transport_header(skb); - max_delay = max(msecs_to_jiffies(mldv2_mrc(mlh2)), 1UL); - idev->mc_maxdelay = max_delay; - - mld_update_qrv(idev, mlh2); - mld_update_qi(idev, mlh2); - mld_update_qri(idev, mlh2); + err = mld_process_v2(idev, mlh2, &max_delay); + if (err < 0) + return err; if (group_type == IPV6_ADDR_ANY) { /* general query */ if (mlh2->mld2q_nsrcs) -- cgit v1.2.3 From b4af8def5c083e5424c66051fe400444a01c4644 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Wed, 4 Sep 2013 00:19:43 +0200 Subject: net: ipv6: mld: introduce mld_{gq, ifc, dad}_stop_timer functions We already have mld_{gq,ifc,dad}_start_timer() functions, so introduce mld_{gq,ifc,dad}_stop_timer() functions to reduce code size and make it more readable. Signed-off-by: Daniel Borkmann Cc: Hannes Frederic Sowa Acked-by: Hannes Frederic Sowa Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 04399cb71e14..096cd67b737c 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -1006,6 +1006,13 @@ static void mld_gq_start_timer(struct inet6_dev *idev) in6_dev_hold(idev); } +static void mld_gq_stop_timer(struct inet6_dev *idev) +{ + idev->mc_gq_running = 0; + if (del_timer(&idev->mc_gq_timer)) + __in6_dev_put(idev); +} + static void mld_ifc_start_timer(struct inet6_dev *idev, unsigned long delay) { unsigned long tv = net_random() % delay; @@ -1014,6 +1021,13 @@ static void mld_ifc_start_timer(struct inet6_dev *idev, unsigned long delay) in6_dev_hold(idev); } +static void mld_ifc_stop_timer(struct inet6_dev *idev) +{ + idev->mc_ifc_count = 0; + if (del_timer(&idev->mc_ifc_timer)) + __in6_dev_put(idev); +} + static void mld_dad_start_timer(struct inet6_dev *idev, unsigned long delay) { unsigned long tv = net_random() % delay; @@ -1022,6 +1036,12 @@ static void mld_dad_start_timer(struct inet6_dev *idev, unsigned long delay) in6_dev_hold(idev); } +static void mld_dad_stop_timer(struct inet6_dev *idev) +{ + if (del_timer(&idev->mc_dad_timer)) + __in6_dev_put(idev); +} + /* * IGMP handling (alias multicast ICMPv6 messages) */ @@ -1234,15 +1254,9 @@ static int mld_process_v1(struct inet6_dev *idev, struct mld_msg *mld, mld_set_v1_mode(idev); /* cancel MLDv2 report timer */ - idev->mc_gq_running = 0; - if (del_timer(&idev->mc_gq_timer)) - __in6_dev_put(idev); - + mld_gq_stop_timer(idev); /* cancel the interface change timer */ - idev->mc_ifc_count = 0; - if (del_timer(&idev->mc_ifc_timer)) - __in6_dev_put(idev); - + mld_ifc_stop_timer(idev); /* clear deleted report items */ mld_clear_delrec(idev); @@ -2434,14 +2448,9 @@ void ipv6_mc_down(struct inet6_dev *idev) /* Withdraw multicast list */ read_lock_bh(&idev->lock); - idev->mc_ifc_count = 0; - if (del_timer(&idev->mc_ifc_timer)) - __in6_dev_put(idev); - idev->mc_gq_running = 0; - if (del_timer(&idev->mc_gq_timer)) - __in6_dev_put(idev); - if (del_timer(&idev->mc_dad_timer)) - __in6_dev_put(idev); + mld_ifc_stop_timer(idev); + mld_gq_stop_timer(idev); + mld_dad_stop_timer(idev); for (i = idev->mc_list; i; i=i->next) igmp6_group_dropped(i); -- cgit v1.2.3 From 9260d3e1013701aa814d10c8fc6a9f92bd17d643 Mon Sep 17 00:00:00 2001 From: Salam Noureddine Date: Sun, 29 Sep 2013 13:41:34 -0700 Subject: ipv6 mcast: use in6_dev_put in timer handlers instead of __in6_dev_put It is possible for the timer handlers to run after the call to ipv6_mc_down so use in6_dev_put instead of __in6_dev_put in the handler function in order to do proper cleanup when the refcnt reaches 0. Otherwise, the refcnt can reach zero without the inet6_dev being destroyed and we end up leaking a reference to the net_device and see messages like the following, unregister_netdevice: waiting for eth0 to become free. Usage count = 1 Tested on linux-3.4.43. Signed-off-by: Salam Noureddine Signed-off-by: David S. Miller --- net/ipv6/mcast.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'net/ipv6/mcast.c') diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 096cd67b737c..d18f9f903db6 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -2034,7 +2034,7 @@ static void mld_dad_timer_expire(unsigned long data) if (idev->mc_dad_count) mld_dad_start_timer(idev, idev->mc_maxdelay); } - __in6_dev_put(idev); + in6_dev_put(idev); } static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode, @@ -2379,7 +2379,7 @@ static void mld_gq_timer_expire(unsigned long data) idev->mc_gq_running = 0; mld_send_report(idev, NULL); - __in6_dev_put(idev); + in6_dev_put(idev); } static void mld_ifc_timer_expire(unsigned long data) @@ -2392,7 +2392,7 @@ static void mld_ifc_timer_expire(unsigned long data) if (idev->mc_ifc_count) mld_ifc_start_timer(idev, idev->mc_maxdelay); } - __in6_dev_put(idev); + in6_dev_put(idev); } static void mld_ifc_event(struct inet6_dev *idev) -- cgit v1.2.3