diff mbox

Fixup packets with incorrect ethertype sent by ZTE MF821D

Message ID 20161113134753.GR2745@pokute.pelzi.net
State Changes Requested, archived
Delegated to: David Miller
Headers show

Commit Message

Jussi Peltola Nov. 13, 2016, 1:47 p.m. UTC
This brokenness appears reliably after running "rdisc6 wwan0" but I have
not debugged if this is related to timing or the format of the router
solicitation. Before receiving a router solicitation, v4 is received
correctly and v6 does not work. After sending the MF821D a router
solicitation with rdisc6, v4 is broken but v6 works. With this patch
(which I am using to write this message) a dual-stack context is usable.

commit 2c25237d19c0c9741c6ebec854def99b88618eac
Author: Jussi Peltola <plz@plz.fi>
Date:   Sun Nov 13 15:41:50 2016 +0200

    Fixup packets with incorrect ethertype sent by ZTE MF821D
    
    Signed-off-by: Jussi Peltola <plz@plz.fi>

Comments

Bjørn Mork Nov. 13, 2016, 4:12 p.m. UTC | #1
Jussi Peltola <plz@plz.fi> writes:

> This brokenness appears reliably after running "rdisc6 wwan0" but I have
> not debugged if this is related to timing or the format of the router
> solicitation. Before receiving a router solicitation, v4 is received
> correctly and v6 does not work. After sending the MF821D a router
> solicitation with rdisc6, v4 is broken but v6 works. With this patch
> (which I am using to write this message) a dual-stack context is usable.
>
> commit 2c25237d19c0c9741c6ebec854def99b88618eac
> Author: Jussi Peltola <plz@plz.fi>
> Date:   Sun Nov 13 15:41:50 2016 +0200
>
>     Fixup packets with incorrect ethertype sent by ZTE MF821D
>     
>     Signed-off-by: Jussi Peltola <plz@plz.fi>
>
> diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c
> index 3ff76c6..edd8172 100644
> --- a/drivers/net/usb/qmi_wwan.c
> +++ b/drivers/net/usb/qmi_wwan.c
> @@ -172,6 +172,12 @@ static const u8 buggy_fw_addr[ETH_ALEN] = {0x00, 0xa0, 0xc6, 0x00, 0x00, 0x00};
>   * Another common firmware bug results in all packets being addressed
>   * to 00:a0:c6:00:00:00 despite the host address being different.
>   * This function will also fixup such packets.
> + *
> + * At least the ZTE MF821D sends IPv4 packets with a bogus ethertype
> + * of 0x63bc, 0xe3bc, 0x63bd or 0xe3bd, and bogus source and
> + * destination MACs after it has received an IPv6 router solicitation
> + * (IPv6 is transmitted correctly). This function will also fix up
> + * such packets.
>   */
>  static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
>  {
> @@ -195,12 +201,21 @@ static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
>                         return 0;
>                 if (is_multicast_ether_addr(skb->data))
>                         return 1;
> +
>                 /* possibly bogus destination - rewrite just in case */
>                 skb_reset_mac_header(skb);
>                 goto fix_dest;
>         default:
>                 if (rawip)
>                         return 0;
> +
> +               /* Bogus ethertype and src/dst mac for v4 on ZTE MF821D */
> +               if ((skb->data[12] & 0x7f) == 0x63
> +                && (skb->data[13] & 0xfe) == 0xbc) {
> +                       proto = htons(ETH_P_IP);
> +                       goto reset_mac;
> +               }
> +
>                 /* pass along other packets without modifications */
>                 return 1;
>         }
> @@ -213,6 +228,7 @@ static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
>         if (skb_headroom(skb) < ETH_HLEN)
>                 return 0;
>         skb_push(skb, ETH_HLEN);
> +reset_mac:
>         skb_reset_mac_header(skb);
>         eth_hdr(skb)->h_proto = proto;
>         eth_zero_addr(eth_hdr(skb)->h_source);


I must admit that I don't like the additional magic stuff at all.  I
don't doubt a second that it fixes the problem for you.  But there is
also no doubt that the firmware is so broken that there is no way we can
know this fixes anything for anyone else.  Where did the firmware get
those magic numbers?  It's definitely garbage.  But we have no reason to
think the garbage is invariable. It could be part of the firmware code,
or a serial number, or your assigned IPv6 prefix, or something else that
changes between different modems.  The fact that it is stable for you is
unfortunately not proof that it is the same for everyone else.

We have rejected similar fixes when this issue has come up earlier (it
looks like a generic Qualcomm firmware issue shared with other vendors
using the same baseband firmware generation). Using raw-ip is the
recommended workaround for these modems.

In any case, if we're going to add a fix like this, then I want it way
more generic.  The only valid ethertypes expected from the modem is IP,
IPV6 or ARP. Testing against those three, resetting anything else to IP,
will at least catch *any* bogus value.

But I'm not convinced I want this additional processing of every packet
just to let Qualcomm go on hiring monkeys-on-crack to write their
firmware. At least not when we do have raw-ip as a workaround for the
issue.

Feel free to try to convince me, though.


Bjørn
David Miller Nov. 13, 2016, 5 p.m. UTC | #2
From: Bjørn Mork <bjorn@mork.no>
Date: Sun, 13 Nov 2016 17:12:57 +0100

> In any case, if we're going to add a fix like this, then I want it way
> more generic.  The only valid ethertypes expected from the modem is IP,
> IPV6 or ARP. Testing against those three, resetting anything else to IP,
> will at least catch *any* bogus value.
> 
> But I'm not convinced I want this additional processing of every packet
> just to let Qualcomm go on hiring monkeys-on-crack to write their
> firmware. At least not when we do have raw-ip as a workaround for the
> issue.
> 
> Feel free to try to convince me, though.

There are also several coding style problems with this patch.
Jussi Peltola Nov. 13, 2016, 5:25 p.m. UTC | #3
On Sun, Nov 13, 2016 at 05:12:57PM +0100, Bjørn Mork wrote:
> In any case, if we're going to add a fix like this, then I want it way
> more generic.  The only valid ethertypes expected from the modem is IP,
> IPV6 or ARP. Testing against those three, resetting anything else to IP,
> will at least catch *any* bogus value.
 
Yes, this is pretty obvious, and it's pretty easy to look for an initial
4 or 6 and assume anything else is ARP (and just pass it through since
no reports of wrong ARP ethertype have been seen as far as I know and
fixing ARP up is probably futile if the L2 addresses in the body or
header don't make sense.)

I didn't bother writing this before sending this one first to provoke
discussion. It should actually be pretty simple; just pop the L2 header
if it looks like it's too broken and later add a new one if (!rawip).

> But I'm not convinced I want this additional processing of every packet
> just to let Qualcomm go on hiring monkeys-on-crack to write their
> firmware. At least not when we do have raw-ip as a workaround for the
> issue.
> 
> Feel free to try to convince me, though.

This is a modem widely sold in Finland by one of the telcos that has
enabled IPv6 to all subscribers. So the population affected is
definitely not just one person - but I will have to see if rawip works
and then see if ModemManager can be made to use that by default. I
didn't initially even think of trying, because v6 only works on this
modem after a router solicit. But who knows...

I find bugs like this, where the general answer for users (or userspace
parts like ModemManager) is "just disable this ipv6 crap" pretty nasty.
Even if the required fix is not exactly elegant, brokenness like this
can greatly set back getting v6 enabled when available.

As more v6 is deployed around the world I would expect more broken
Qualcomm modems to show themselves. I'll have to try to test this modem
to see if this bug disappears when the telco does not have IPv6; if so,
people are in for annoying surprises as the modem suddenly stops working
when the telco deploys v6.

I can agree with the lack of elegance. But I don't see processing per
every packet as a significant issue when the devices connect over USB
and their transfer rates are limited by the real world performance of
mobile networks, and the other option is that the device just doesn't
work at all. A knob would definitely feel wrong, as there is no
indication this logic will ever break any functionality for anyone, it
just wastes a few CPU cycles if the modem is not broken.
diff mbox

Patch

diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c
index 3ff76c6..edd8172 100644
--- a/drivers/net/usb/qmi_wwan.c
+++ b/drivers/net/usb/qmi_wwan.c
@@ -172,6 +172,12 @@  static const u8 buggy_fw_addr[ETH_ALEN] = {0x00, 0xa0, 0xc6, 0x00, 0x00, 0x00};
  * Another common firmware bug results in all packets being addressed
  * to 00:a0:c6:00:00:00 despite the host address being different.
  * This function will also fixup such packets.
+ *
+ * At least the ZTE MF821D sends IPv4 packets with a bogus ethertype
+ * of 0x63bc, 0xe3bc, 0x63bd or 0xe3bd, and bogus source and
+ * destination MACs after it has received an IPv6 router solicitation
+ * (IPv6 is transmitted correctly). This function will also fix up
+ * such packets.
  */
 static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
 {
@@ -195,12 +201,21 @@  static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
                        return 0;
                if (is_multicast_ether_addr(skb->data))
                        return 1;
+
                /* possibly bogus destination - rewrite just in case */
                skb_reset_mac_header(skb);
                goto fix_dest;
        default:
                if (rawip)
                        return 0;
+
+               /* Bogus ethertype and src/dst mac for v4 on ZTE MF821D */
+               if ((skb->data[12] & 0x7f) == 0x63
+                && (skb->data[13] & 0xfe) == 0xbc) {
+                       proto = htons(ETH_P_IP);
+                       goto reset_mac;
+               }
+
                /* pass along other packets without modifications */
                return 1;
        }
@@ -213,6 +228,7 @@  static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
        if (skb_headroom(skb) < ETH_HLEN)
                return 0;
        skb_push(skb, ETH_HLEN);
+reset_mac:
        skb_reset_mac_header(skb);
        eth_hdr(skb)->h_proto = proto;
        eth_zero_addr(eth_hdr(skb)->h_source);