Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ipv6: mld: answer mldv2 queries with mldv1 reports in mldv1 fallback

RFC2710 (MLDv1), section 3.7. says:

The length of a received MLD message is computed by taking the
IPv6 Payload Length value and subtracting the length of any IPv6
extension headers present between the IPv6 header and the MLD
message. If that length is greater than 24 octets, that indicates
that there are other fields present *beyond* the fields described
above, perhaps belonging to a *future backwards-compatible* version
of MLD. An implementation of the version of MLD specified in this
document *MUST NOT* send an MLD message longer than 24 octets and
MUST ignore anything past the first 24 octets of a received MLD
message.

RFC3810 (MLDv2), section 8.2.1. states for *listeners* regarding
presence of MLDv1 routers:

In order to be compatible with MLDv1 routers, MLDv2 hosts MUST
operate in version 1 compatibility mode. [...] When Host
Compatibility Mode is MLDv2, a host acts using the MLDv2 protocol
on that interface. When Host Compatibility Mode is MLDv1, a host
acts in MLDv1 compatibility mode, using *only* the MLDv1 protocol,
on that interface. [...]

While section 8.3.1. specifies *router* behaviour regarding presence
of MLDv1 routers:

MLDv2 routers may be placed on a network where there is at least
one MLDv1 router. The following requirements apply:

If an MLDv1 router is present on the link, the Querier MUST use
the *lowest* version of MLD present on the network. This must be
administratively assured. Routers that desire to be compatible
with MLDv1 MUST have a configuration option to act in MLDv1 mode;
if an MLDv1 router is present on the link, the system administrator
must explicitly configure all MLDv2 routers to act in MLDv1 mode.
When in MLDv1 mode, the Querier MUST send periodic General Queries
truncated at the Multicast Address field (i.e., 24 bytes long),
and SHOULD also warn about receiving an MLDv2 Query (such warnings
must be rate-limited). The Querier MUST also fill in the Maximum
Response Delay in the Maximum Response Code field, i.e., the
exponential algorithm described in section 5.1.3. is not used. [...]

That means that we should not get queries from different versions of
MLD. When there's a MLDv1 router present, MLDv2 enforces truncation
and MRC == MRD (both fields are overlapping within the 24 octet range).

Section 8.3.2. specifies behaviour in the presence of MLDv1 multicast
address *listeners*:

MLDv2 routers may be placed on a network where there are hosts
that have not yet been upgraded to MLDv2. In order to be compatible
with MLDv1 hosts, MLDv2 routers MUST operate in version 1 compatibility
mode. MLDv2 routers keep a compatibility mode per multicast address
record. The compatibility mode of a multicast address is determined
from the Multicast Address Compatibility Mode variable, which can be
in one of the two following states: MLDv1 or MLDv2.

The Multicast Address Compatibility Mode of a multicast address
record is set to MLDv1 whenever an MLDv1 Multicast Listener Report is
*received* for that multicast address. At the same time, the Older
Version Host Present timer for the multicast address is set to Older
Version Host Present Timeout seconds. The timer is re-set whenever a
new MLDv1 Report is received for that multicast address. If the Older
Version Host Present timer expires, the router switches back to
Multicast Address Compatibility Mode of MLDv2 for that multicast
address. [...]

That means, what can happen is the following scenario, that hosts can
act in MLDv1 compatibility mode when they previously have received an
MLDv1 query (or, simply operate in MLDv1 mode-only); and at the same
time, an MLDv2 router could start up and transmits MLDv2 startup query
messages while being unaware of the current operational mode.

Given RFC2710, section 3.7 we would need to answer to that with an MLDv1
listener report, so that the router according to RFC3810, section 8.3.2.
would receive that and internally switch to MLDv1 compatibility as well.

Right now, I believe since the initial implementation of MLDv2, Linux
hosts would just silently drop such MLDv2 queries instead of replying
with an MLDv1 listener report, which would prevent a MLDv2 router going
into fallback mode (until it receives other MLDv1 queries).

Since the mapping of MRC to MRD in exactly such cases can make use of
the exponential algorithm from 5.1.3, we cannot [strictly speaking] be
aware in MLDv1 of the encoding in MRC, it seems also not mentioned by
the RFC. Since encodings are the same up to 32767, assume in such a
situation this value as a hard upper limit we would clamp. We have asked
one of the RFC authors on that regard, and he mentioned that there seem
not to be any implementations that make use of that exponential algorithm
on startup messages. In any case, this patch fixes this MLD
interoperability issue.

Signed-off-by: Daniel Borkmann <dborkman@redhat.com>
Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Daniel Borkmann and committed by
David S. Miller
35f7aa53 25ee7327

+35 -11
+4 -1
include/net/mld.h
··· 88 88 #define MLDV2_QQIC_EXP(value) (((value) >> 4) & 0x07) 89 89 #define MLDV2_QQIC_MAN(value) ((value) & 0x0f) 90 90 91 + #define MLD_EXP_MIN_LIMIT 32768UL 92 + #define MLDV1_MRD_MAX_COMPAT (MLD_EXP_MIN_LIMIT - 1) 93 + 91 94 static inline unsigned long mldv2_mrc(const struct mld2_query *mlh2) 92 95 { 93 96 /* RFC3810, 5.1.3. Maximum Response Code */ 94 97 unsigned long ret, mc_mrc = ntohs(mlh2->mld2q_mrc); 95 98 96 - if (mc_mrc < 32768) { 99 + if (mc_mrc < MLD_EXP_MIN_LIMIT) { 97 100 ret = mc_mrc; 98 101 } else { 99 102 unsigned long mc_man, mc_exp;
+31 -10
net/ipv6/mcast.c
··· 1237 1237 } 1238 1238 1239 1239 static int mld_process_v1(struct inet6_dev *idev, struct mld_msg *mld, 1240 - unsigned long *max_delay) 1240 + unsigned long *max_delay, bool v1_query) 1241 1241 { 1242 1242 unsigned long mldv1_md; 1243 1243 ··· 1245 1245 if (mld_in_v2_mode_only(idev)) 1246 1246 return -EINVAL; 1247 1247 1248 - /* MLDv1 router present */ 1249 1248 mldv1_md = ntohs(mld->mld_maxdelay); 1249 + 1250 + /* When in MLDv1 fallback and a MLDv2 router start-up being 1251 + * unaware of current MLDv1 operation, the MRC == MRD mapping 1252 + * only works when the exponential algorithm is not being 1253 + * used (as MLDv1 is unaware of such things). 1254 + * 1255 + * According to the RFC author, the MLDv2 implementations 1256 + * he's aware of all use a MRC < 32768 on start up queries. 1257 + * 1258 + * Thus, should we *ever* encounter something else larger 1259 + * than that, just assume the maximum possible within our 1260 + * reach. 1261 + */ 1262 + if (!v1_query) 1263 + mldv1_md = min(mldv1_md, MLDV1_MRD_MAX_COMPAT); 1264 + 1250 1265 *max_delay = max(msecs_to_jiffies(mldv1_md), 1UL); 1251 1266 1252 - mld_set_v1_mode(idev); 1267 + /* MLDv1 router present: we need to go into v1 mode *only* 1268 + * when an MLDv1 query is received as per section 9.12. of 1269 + * RFC3810! And we know from RFC2710 section 3.7 that MLDv1 1270 + * queries MUST be of exactly 24 octets. 1271 + */ 1272 + if (v1_query) 1273 + mld_set_v1_mode(idev); 1253 1274 1254 1275 /* cancel MLDv2 report timer */ 1255 1276 mld_gq_stop_timer(idev); ··· 1285 1264 static int mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld, 1286 1265 unsigned long *max_delay) 1287 1266 { 1288 - /* hosts need to stay in MLDv1 mode, discard MLDv2 queries */ 1289 - if (mld_in_v1_mode(idev)) 1290 - return -EINVAL; 1291 - 1292 1267 *max_delay = max(msecs_to_jiffies(mldv2_mrc(mld)), 1UL); 1293 1268 1294 1269 mld_update_qrv(idev, mld); ··· 1341 1324 !(group_type&IPV6_ADDR_MULTICAST)) 1342 1325 return -EINVAL; 1343 1326 1344 - if (len == MLD_V1_QUERY_LEN) { 1345 - err = mld_process_v1(idev, mld, &max_delay); 1327 + if (len < MLD_V1_QUERY_LEN) { 1328 + return -EINVAL; 1329 + } else if (len == MLD_V1_QUERY_LEN || mld_in_v1_mode(idev)) { 1330 + err = mld_process_v1(idev, mld, &max_delay, 1331 + len == MLD_V1_QUERY_LEN); 1346 1332 if (err < 0) 1347 1333 return err; 1348 1334 } else if (len >= MLD_V2_QUERY_LEN_MIN) { ··· 1377 1357 mlh2 = (struct mld2_query *)skb_transport_header(skb); 1378 1358 mark = 1; 1379 1359 } 1380 - } else 1360 + } else { 1381 1361 return -EINVAL; 1362 + } 1382 1363 1383 1364 read_lock_bh(&idev->lock); 1384 1365 if (group_type == IPV6_ADDR_ANY) {