aboutsummaryrefslogtreecommitdiffstats
path: root/net/ipv6/mcast.c
diff options
context:
space:
mode:
authorDaniel Borkmann <dborkman@redhat.com>2013-09-03 18:19:37 -0400
committerDavid S. Miller <davem@davemloft.net>2013-09-04 14:53:20 -0400
commit89225d1ce6af3916bf32aecbe9d83f571a098588 (patch)
tree0c4c921e137a42d6d2e6c65d3879fd33bbc2702b /net/ipv6/mcast.c
parent52f20e655d9f6f7f937a1cdacf219d9df3ab6166 (diff)
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 <dborkman@redhat.com> Cc: David Stevens <dlstevens@us.ibm.com> Cc: Hannes Frederic Sowa <hannes@stressinduktion.org> Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv6/mcast.c')
-rw-r--r--net/ipv6/mcast.c116
1 files changed, 110 insertions, 6 deletions
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,
108 struct inet6_dev *idev); 108 struct inet6_dev *idev);
109 109
110#define MLD_QRV_DEFAULT 2 110#define MLD_QRV_DEFAULT 2
111/* RFC3810, 9.2. Query Interval */
112#define MLD_QI_DEFAULT (125 * HZ)
113/* RFC3810, 9.3. Query Response Interval */
114#define MLD_QRI_DEFAULT (10 * HZ)
111 115
112/* RFC3810, 8.1 Query Version Distinctions */ 116/* RFC3810, 8.1 Query Version Distinctions */
113#define MLD_V1_QUERY_LEN 24 117#define MLD_V1_QUERY_LEN 24
@@ -1112,6 +1116,93 @@ static bool mld_marksources(struct ifmcaddr6 *pmc, int nsrcs,
1112 return true; 1116 return true;
1113} 1117}
1114 1118
1119static void mld_set_v1_mode(struct inet6_dev *idev)
1120{
1121 /* RFC3810, relevant sections:
1122 * - 9.1. Robustness Variable
1123 * - 9.2. Query Interval
1124 * - 9.3. Query Response Interval
1125 * - 9.12. Older Version Querier Present Timeout
1126 */
1127 unsigned long switchback;
1128
1129 switchback = (idev->mc_qrv * idev->mc_qi) + idev->mc_qri;
1130
1131 idev->mc_v1_seen = jiffies + switchback;
1132}
1133
1134static void mld_update_qrv(struct inet6_dev *idev,
1135 const struct mld2_query *mlh2)
1136{
1137 /* RFC3810, relevant sections:
1138 * - 5.1.8. QRV (Querier's Robustness Variable)
1139 * - 9.1. Robustness Variable
1140 */
1141
1142 /* The value of the Robustness Variable MUST NOT be zero,
1143 * and SHOULD NOT be one. Catch this here if we ever run
1144 * into such a case in future.
1145 */
1146 WARN_ON(idev->mc_qrv == 0);
1147
1148 if (mlh2->mld2q_qrv > 0)
1149 idev->mc_qrv = mlh2->mld2q_qrv;
1150
1151 if (unlikely(idev->mc_qrv < 2)) {
1152 net_warn_ratelimited("IPv6: MLD: clamping QRV from %u to %u!\n",
1153 idev->mc_qrv, MLD_QRV_DEFAULT);
1154 idev->mc_qrv = MLD_QRV_DEFAULT;
1155 }
1156}
1157
1158static void mld_update_qi(struct inet6_dev *idev,
1159 const struct mld2_query *mlh2)
1160{
1161 /* RFC3810, relevant sections:
1162 * - 5.1.9. QQIC (Querier's Query Interval Code)
1163 * - 9.2. Query Interval
1164 * - 9.12. Older Version Querier Present Timeout
1165 * (the [Query Interval] in the last Query received)
1166 */
1167 unsigned long mc_qqi;
1168
1169 if (mlh2->mld2q_qqic < 128) {
1170 mc_qqi = mlh2->mld2q_qqic;
1171 } else {
1172 unsigned long mc_man, mc_exp;
1173
1174 mc_exp = MLDV2_QQIC_EXP(mlh2->mld2q_qqic);
1175 mc_man = MLDV2_QQIC_MAN(mlh2->mld2q_qqic);
1176
1177 mc_qqi = (mc_man | 0x10) << (mc_exp + 3);
1178 }
1179
1180 idev->mc_qi = mc_qqi * HZ;
1181}
1182
1183static void mld_update_qri(struct inet6_dev *idev,
1184 const struct mld2_query *mlh2)
1185{
1186 /* RFC3810, relevant sections:
1187 * - 5.1.3. Maximum Response Code
1188 * - 9.3. Query Response Interval
1189 */
1190 unsigned long mc_qri, mc_mrc = ntohs(mlh2->mld2q_mrc);
1191
1192 if (mc_mrc < 32768) {
1193 mc_qri = mc_mrc;
1194 } else {
1195 unsigned long mc_man, mc_exp;
1196
1197 mc_exp = MLDV2_MRC_EXP(mc_mrc);
1198 mc_man = MLDV2_MRC_MAN(mc_mrc);
1199
1200 mc_qri = (mc_man | 0x1000) << (mc_exp + 3);
1201 }
1202
1203 idev->mc_qri = msecs_to_jiffies(mc_qri);
1204}
1205
1115/* called with rcu_read_lock() */ 1206/* called with rcu_read_lock() */
1116int igmp6_event_query(struct sk_buff *skb) 1207int igmp6_event_query(struct sk_buff *skb)
1117{ 1208{
@@ -1150,12 +1241,15 @@ int igmp6_event_query(struct sk_buff *skb)
1150 return -EINVAL; 1241 return -EINVAL;
1151 1242
1152 if (len == MLD_V1_QUERY_LEN) { 1243 if (len == MLD_V1_QUERY_LEN) {
1153 int switchback;
1154 /* MLDv1 router present */ 1244 /* MLDv1 router present */
1155
1156 max_delay = msecs_to_jiffies(ntohs(mld->mld_maxdelay)); 1245 max_delay = msecs_to_jiffies(ntohs(mld->mld_maxdelay));
1157 switchback = (idev->mc_qrv + 1) * max_delay; 1246
1158 idev->mc_v1_seen = jiffies + switchback; 1247 mld_set_v1_mode(idev);
1248
1249 /* cancel MLDv2 report timer */
1250 idev->mc_gq_running = 0;
1251 if (del_timer(&idev->mc_gq_timer))
1252 __in6_dev_put(idev);
1159 1253
1160 /* cancel the interface change timer */ 1254 /* cancel the interface change timer */
1161 idev->mc_ifc_count = 0; 1255 idev->mc_ifc_count = 0;
@@ -1166,6 +1260,10 @@ int igmp6_event_query(struct sk_buff *skb)
1166 } else if (len >= MLD_V2_QUERY_LEN_MIN) { 1260 } else if (len >= MLD_V2_QUERY_LEN_MIN) {
1167 int srcs_offset = sizeof(struct mld2_query) - 1261 int srcs_offset = sizeof(struct mld2_query) -
1168 sizeof(struct icmp6hdr); 1262 sizeof(struct icmp6hdr);
1263
1264 /* hosts need to stay in MLDv1 mode, discard MLDv2 queries */
1265 if (MLD_V1_SEEN(idev))
1266 return 0;
1169 if (!pskb_may_pull(skb, srcs_offset)) 1267 if (!pskb_may_pull(skb, srcs_offset))
1170 return -EINVAL; 1268 return -EINVAL;
1171 1269
@@ -1175,8 +1273,10 @@ int igmp6_event_query(struct sk_buff *skb)
1175 1273
1176 idev->mc_maxdelay = max_delay; 1274 idev->mc_maxdelay = max_delay;
1177 1275
1178 if (mlh2->mld2q_qrv) 1276 mld_update_qrv(idev, mlh2);
1179 idev->mc_qrv = mlh2->mld2q_qrv; 1277 mld_update_qi(idev, mlh2);
1278 mld_update_qri(idev, mlh2);
1279
1180 if (group_type == IPV6_ADDR_ANY) { /* general query */ 1280 if (group_type == IPV6_ADDR_ANY) { /* general query */
1181 if (mlh2->mld2q_nsrcs) 1281 if (mlh2->mld2q_nsrcs)
1182 return -EINVAL; /* no sources allowed */ 1282 return -EINVAL; /* no sources allowed */
@@ -2337,7 +2437,11 @@ void ipv6_mc_init_dev(struct inet6_dev *idev)
2337 (unsigned long)idev); 2437 (unsigned long)idev);
2338 setup_timer(&idev->mc_dad_timer, mld_dad_timer_expire, 2438 setup_timer(&idev->mc_dad_timer, mld_dad_timer_expire,
2339 (unsigned long)idev); 2439 (unsigned long)idev);
2440
2340 idev->mc_qrv = MLD_QRV_DEFAULT; 2441 idev->mc_qrv = MLD_QRV_DEFAULT;
2442 idev->mc_qi = MLD_QI_DEFAULT;
2443 idev->mc_qri = MLD_QRI_DEFAULT;
2444
2341 idev->mc_maxdelay = unsolicited_report_interval(idev); 2445 idev->mc_maxdelay = unsolicited_report_interval(idev);
2342 idev->mc_v1_seen = 0; 2446 idev->mc_v1_seen = 0;
2343 write_unlock_bh(&idev->lock); 2447 write_unlock_bh(&idev->lock);