Skip to content

Commit 35f7aa5

Browse files
Daniel Borkmanndavem330
authored andcommitted
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>
1 parent 25ee732 commit 35f7aa5

File tree

2 files changed

+35
-11
lines changed

2 files changed

+35
-11
lines changed

include/net/mld.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,15 @@ struct mld2_query {
8888
#define MLDV2_QQIC_EXP(value) (((value) >> 4) & 0x07)
8989
#define MLDV2_QQIC_MAN(value) ((value) & 0x0f)
9090

91+
#define MLD_EXP_MIN_LIMIT 32768UL
92+
#define MLDV1_MRD_MAX_COMPAT (MLD_EXP_MIN_LIMIT - 1)
93+
9194
static inline unsigned long mldv2_mrc(const struct mld2_query *mlh2)
9295
{
9396
/* RFC3810, 5.1.3. Maximum Response Code */
9497
unsigned long ret, mc_mrc = ntohs(mlh2->mld2q_mrc);
9598

96-
if (mc_mrc < 32768) {
99+
if (mc_mrc < MLD_EXP_MIN_LIMIT) {
97100
ret = mc_mrc;
98101
} else {
99102
unsigned long mc_man, mc_exp;

net/ipv6/mcast.c

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,19 +1237,40 @@ static void mld_update_qri(struct inet6_dev *idev,
12371237
}
12381238

12391239
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)
12411241
{
12421242
unsigned long mldv1_md;
12431243

12441244
/* Ignore v1 queries */
12451245
if (mld_in_v2_mode_only(idev))
12461246
return -EINVAL;
12471247

1248-
/* MLDv1 router present */
12491248
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+
12501265
*max_delay = max(msecs_to_jiffies(mldv1_md), 1UL);
12511266

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);
12531274

12541275
/* cancel MLDv2 report timer */
12551276
mld_gq_stop_timer(idev);
@@ -1264,10 +1285,6 @@ static int mld_process_v1(struct inet6_dev *idev, struct mld_msg *mld,
12641285
static int mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld,
12651286
unsigned long *max_delay)
12661287
{
1267-
/* hosts need to stay in MLDv1 mode, discard MLDv2 queries */
1268-
if (mld_in_v1_mode(idev))
1269-
return -EINVAL;
1270-
12711288
*max_delay = max(msecs_to_jiffies(mldv2_mrc(mld)), 1UL);
12721289

12731290
mld_update_qrv(idev, mld);
@@ -1324,8 +1341,11 @@ int igmp6_event_query(struct sk_buff *skb)
13241341
!(group_type&IPV6_ADDR_MULTICAST))
13251342
return -EINVAL;
13261343

1327-
if (len == MLD_V1_QUERY_LEN) {
1328-
err = mld_process_v1(idev, mld, &max_delay);
1344+
if (len < MLD_V1_QUERY_LEN) {
1345+
return -EINVAL;
1346+
} else if (len == MLD_V1_QUERY_LEN || mld_in_v1_mode(idev)) {
1347+
err = mld_process_v1(idev, mld, &max_delay,
1348+
len == MLD_V1_QUERY_LEN);
13291349
if (err < 0)
13301350
return err;
13311351
} else if (len >= MLD_V2_QUERY_LEN_MIN) {
@@ -1357,8 +1377,9 @@ int igmp6_event_query(struct sk_buff *skb)
13571377
mlh2 = (struct mld2_query *)skb_transport_header(skb);
13581378
mark = 1;
13591379
}
1360-
} else
1380+
} else {
13611381
return -EINVAL;
1382+
}
13621383

13631384
read_lock_bh(&idev->lock);
13641385
if (group_type == IPV6_ADDR_ANY) {

0 commit comments

Comments
 (0)