diff mbox series

[2/3] rndis_host: enable the bogus MAC fixup for ZTE devices from cdc_ether

Message ID 20220407001926.11252-3-lech.perczak@gmail.com
State New
Headers show
Series rndis_host: handle bogus MAC addresses in ZTE RNDIS devices | expand

Commit Message

Lech Perczak April 7, 2022, 12:19 a.m. UTC
Certain ZTE modems, namely: MF823. MF831, MF910, built-in modem from
MF286R, expose both CDC-ECM and RNDIS network interfaces.
They have a trait of ignoring the locally-administered MAC address
configured on the interface both in CDC-ECM and RNDIS part,
and this leads to dropping of incoming traffic by the host.
However, the workaround was only present in CDC-ECM, and MF286R
explicitly requires it in RNDIS mode.

Re-use the workaround in rndis_host as well, to fix operation of MF286R
module, some versions of which expose only the RNDIS interface.

Apply the workaround to both "flavors" of RNDIS interfaces, as older ZTE
modems, like MF823 found in the wild, report the USB_CLASS_COMM class
interfaces, while MF286R reports USB_CLASS_WIRELESS_CONTROLLER.

Cc: Kristian Evensen <kristian.evensen@gmail.com>
Cc: Bjørn Mork <bjorn@mork.no>
Cc: Oliver Neukum <oliver@neukum.org>
Signed-off-by: Lech Perczak <lech.perczak@gmail.com>
---
 drivers/net/usb/rndis_host.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

Comments

Lech Perczak April 7, 2022, 8:51 p.m. UTC | #1
Hi Bjørn,

Many thanks you for your review! Answers inline.

W dniu 2022-04-07 o 08:25, Bjørn Mork pisze:
> Lech Perczak <lech.perczak@gmail.com> writes:
>
>> +static int zte_rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
>> +{
>> +	return rndis_rx_fixup(dev, skb) && usbnet_cdc_zte_rx_fixup(dev, skb);
>> +}
>>   
> Does this work as expected? Only the last ethernet packet in the rndis
> frame will end up being handled by usbnet_cdc_zte_rx_fixup().  The
> others are cloned and submitted directly to usbnet_skb_return().
I've got some positive reports from at least two owners of the device - 
I don't have one myself. In the meantime asked them to run tests with 
high traffic, because this should most probably manifest itself in that 
scenario easily - my wild guess is that the modem doesn't use batching, 
but you are most certainly right in the general case. And for testing on 
older modems, we can probably only count on Kristian.
>
> I don't know how to best solve that, but maybe add another
> RNDIS_DRIVER_DATA_x flag and test that in rndis_rx_fixup?  I.e something
> like
>
> 	bool fixup_dst = dev->driver_info->data & RNDIS_DRIVER_DATA_FIXUP_DST:
>          ..
>
> 		/* try to return all the packets in the batch */
> 		skb2 = skb_clone(skb, GFP_ATOMIC);
> 		if (unlikely(!skb2))
> 			break;
> 		skb_pull(skb, msg_len - sizeof *hdr);
> 		skb_trim(skb2, data_len);
>                  if (fixup_dst)
>                  	usbnet_cdc_zte_rx_fixup(dev, skb2);
> 		usbnet_skb_return(dev, skb2);
> 	}
>          if (fixup_dst)
>                  usbnet_cdc_zte_rx_fixup(dev, skb);
>
> 	/* caller will usbnet_skb_return the remaining packet */
> 	return 1;
> }

I'll consider that. My concern with that approach is degradation of 
performance by testing for that flag, both for ZTE and non-ZTE devices, 
for each and every packet. But this might be the only solution, as I 
cannot catch the n-1 sk_buffs from the batch mid-flight, only the last 
one. The only other way that currently comes to my mind, is to duplicate 
rndis_rx_fixup, with added calls to usbnet_cdc_zte_rx_fixup in the right 
places. But the amount of duplicated code by doing so would be huge, so 
I'd like to avoid that as well.

I will definitely send a V2 after I decide on a solution and do some 
testing, including high downlink traffic.

>
>
>
> Bjørn
Lech Perczak April 12, 2022, 10:32 p.m. UTC | #2
W dniu 2022-04-07 o 22:51, Lech Perczak pisze:
> Hi Bjørn,
>
> Many thanks you for your review! Answers inline.
>
> W dniu 2022-04-07 o 08:25, Bjørn Mork pisze:
>> Lech Perczak <lech.perczak@gmail.com> writes:
>>
>>> +static int zte_rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
>>> +{
>>> +    return rndis_rx_fixup(dev, skb) && usbnet_cdc_zte_rx_fixup(dev, 
>>> skb);
>>> +}
>> Does this work as expected? Only the last ethernet packet in the rndis
>> frame will end up being handled by usbnet_cdc_zte_rx_fixup(). The
>> others are cloned and submitted directly to usbnet_skb_return().
> I've got some positive reports from at least two owners of the device 
> - I don't have one myself. In the meantime asked them to run tests 
> with high traffic, because this should most probably manifest itself 
> in that scenario easily - my wild guess is that the modem doesn't use 
> batching, but you are most certainly right in the general case. And 
> for testing on older modems, we can probably only count on Kristian.
>>
>> I don't know how to best solve that, but maybe add another
>> RNDIS_DRIVER_DATA_x flag and test that in rndis_rx_fixup?  I.e something
>> like
>>
>>     bool fixup_dst = dev->driver_info->data & 
>> RNDIS_DRIVER_DATA_FIXUP_DST:
>>          ..
>>
>>         /* try to return all the packets in the batch */
>>         skb2 = skb_clone(skb, GFP_ATOMIC);
>>         if (unlikely(!skb2))
>>             break;
>>         skb_pull(skb, msg_len - sizeof *hdr);
>>         skb_trim(skb2, data_len);
>>                  if (fixup_dst)
>>                      usbnet_cdc_zte_rx_fixup(dev, skb2);
>>         usbnet_skb_return(dev, skb2);
>>     }
>>          if (fixup_dst)
>>                  usbnet_cdc_zte_rx_fixup(dev, skb);
>>
>>     /* caller will usbnet_skb_return the remaining packet */
>>     return 1;
>> }
>
> I'll consider that. My concern with that approach is degradation of 
> performance by testing for that flag, both for ZTE and non-ZTE 
> devices, for each and every packet. But this might be the only 
> solution, as I cannot catch the n-1 sk_buffs from the batch 
> mid-flight, only the last one. The only other way that currently comes 
> to my mind, is to duplicate rndis_rx_fixup, with added calls to 
> usbnet_cdc_zte_rx_fixup in the right places. But the amount of 
> duplicated code by doing so would be huge, so I'd like to avoid that 
> as well.
>
> I will definitely send a V2 after I decide on a solution and do some 
> testing, including high downlink traffic.
>
>>
>>
>>
>> Bjørn
>
Hi Bjørn,

I implemented the fix according to your suggestion and did some testing.
Although I don't have a full MF286R myself, I used my Raspberry Pi 
Zero's USB gadget
to simulate the modem's RNDIS interface, and compared three scenarios:
- generic VID/PID with "locally administered" bit off,
- generic VID/PID with "locally administered" bit on,
- ZTE VID/PID (of MF286R's modem) with "locally administered" bit on.

Of course, only the last one activated the MAC fixup path.

For testing I used one of my modem-less MF286A cross-flashed to MF286R 
using current
OpenWrt master - which are exactly the same hardware, modulo the 
internal modem.
In all three scenarios, when running iperf3 server on the Pi Zero,
I got constant 150Mbps of traffic in both directions, with iperf3 client 
running on the
router itself. When router was uploading data to my "modem", CPU usage 
was around 66%.
When downloading, the total usage would hit 100%, with about 15% 
attributed to syscalls,
and about 85% attributed to softirq.
When using iperf3 client on a PC connected to the router, and enabling 
flow offload,
the softirq load would drop to around 75%, and CPU would idle for the 
rest of time,
in both directions, but the downlink speed would drop to around 125Mbps, 
with upload the
same as if running iperf3 on router itself.

I compared all of this against a build without this patchset, with 
scenario one - getting
exactly  the same performance.
So, suumming up - it seems my concerns about performance were 
exaggerated, so I decided to just
introduce the check inside zte_rndis_fixup(), just as suggested in this 
thread. V2 coming shortly.

One strange quirk I noticed while testing, is that when "locally 
administered" bit in MAC address
was set, the interface would get "usb" prefix on the host side, and 
"eth" otherwise.
diff mbox series

Patch

diff --git a/drivers/net/usb/rndis_host.c b/drivers/net/usb/rndis_host.c
index 247f58cb0f84..a7eb032115e8 100644
--- a/drivers/net/usb/rndis_host.c
+++ b/drivers/net/usb/rndis_host.c
@@ -578,6 +578,10 @@  rndis_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
 }
 EXPORT_SYMBOL_GPL(rndis_tx_fixup);
 
+static int zte_rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	return rndis_rx_fixup(dev, skb) && usbnet_cdc_zte_rx_fixup(dev, skb);
+}
 
 static const struct driver_info	rndis_info = {
 	.description =	"RNDIS device",
@@ -600,6 +604,16 @@  static const struct driver_info	rndis_poll_status_info = {
 	.tx_fixup =	rndis_tx_fixup,
 };
 
+static const struct driver_info	zte_rndis_info = {
+	.description =	"ZTE RNDIS device",
+	.flags =	FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_FRAMING_RN | FLAG_NO_SETINT,
+	.bind =		rndis_bind,
+	.unbind =	rndis_unbind,
+	.status =	rndis_status,
+	.rx_fixup =	zte_rndis_rx_fixup,
+	.tx_fixup =	rndis_tx_fixup,
+};
+
 /*-------------------------------------------------------------------------*/
 
 static const struct usb_device_id	products [] = {
@@ -613,6 +627,16 @@  static const struct usb_device_id	products [] = {
 	USB_VENDOR_AND_INTERFACE_INFO(0x238b,
 				      USB_CLASS_COMM, 2 /* ACM */, 0x0ff),
 	.driver_info = (unsigned long)&rndis_info,
+}, {
+	/* ZTE WWAN modules */
+	USB_VENDOR_AND_INTERFACE_INFO(0x19d2,
+				      USB_CLASS_WIRELESS_CONTROLLER, 1, 3),
+	.driver_info = (unsigned long)&zte_rndis_info,
+}, {
+	/* ZTE WWAN modules, ACM flavour */
+	USB_VENDOR_AND_INTERFACE_INFO(0x19d2,
+				      USB_CLASS_COMM, 2 /* ACM */, 0x0ff),
+	.driver_info = (unsigned long)&zte_rndis_info,
 }, {
 	/* RNDIS is MSFT's un-official variant of CDC ACM */
 	USB_INTERFACE_INFO(USB_CLASS_COMM, 2 /* ACM */, 0x0ff),