kernel: backport ipeth CDC NCM support
authorGeorgi Valkov <gvalkov@gmail.com>
Fri, 19 Apr 2024 07:19:00 +0000 (10:19 +0300)
committerChristian Marangi <ansuelsmth@gmail.com>
Sun, 21 Apr 2024 20:55:37 +0000 (22:55 +0200)
Fixes no communication with tethered iOS devices in CDC NCM mode.
Freshly booted iOS devices start in legacy mode, but are put into
NCM mode by the official Apple driver.

[1] https://github.com/torvalds/linux/commit/a2d274c62e44b1995c170595db3865c6fe701226

Fixes: #12566
Tested-by: Georgi Valkov <gvalkov@gmail.com>
Signed-off-by: Foster Snowhill <forst@pen.gy>
Signed-off-by: Georgi Valkov <gvalkov@gmail.com>
[ better reference fixed issue ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
(cherry picked from commit 680f8738d02a1876ae4cd11aacf9cd56e520fadf)

target/linux/generic/backport-5.15/796-v6.5-01-usbnet-ipheth-fix-risk-of-NULL-pointer-deallocation.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/796-v6.5-02-usbnet-ipheth-transmit-URBs-without-trailing-padding.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/796-v6.5-03-usbnet-ipheth-add-CDC-NCM-support.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/796-v6.5-04-usbnet-ipheth-update-Kconfig-description.patch [new file with mode: 0644]

diff --git a/target/linux/generic/backport-5.15/796-v6.5-01-usbnet-ipheth-fix-risk-of-NULL-pointer-deallocation.patch b/target/linux/generic/backport-5.15/796-v6.5-01-usbnet-ipheth-fix-risk-of-NULL-pointer-deallocation.patch
new file mode 100644 (file)
index 0000000..d9d6f36
--- /dev/null
@@ -0,0 +1,30 @@
+From 2203718c2f59ffdd6c78d54e5add594aebb4461e Mon Sep 17 00:00:00 2001
+From: Georgi Valkov <gvalkov@gmail.com>
+Date: Wed, 7 Jun 2023 15:56:59 +0200
+Subject: [PATCH 1/4] usbnet: ipheth: fix risk of NULL pointer deallocation
+
+The cleanup precedure in ipheth_probe will attempt to free a
+NULL pointer in dev->ctrl_buf if the memory allocation for
+this buffer is not successful. While kfree ignores NULL pointers,
+and the existing code is safe, it is a better design to rearrange
+the goto labels and avoid this.
+
+Signed-off-by: Georgi Valkov <gvalkov@gmail.com>
+Signed-off-by: Foster Snowhill <forst@pen.gy>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/usb/ipheth.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/usb/ipheth.c
++++ b/drivers/net/usb/ipheth.c
+@@ -510,8 +510,8 @@ err_register_netdev:
+       ipheth_free_urbs(dev);
+ err_alloc_urbs:
+ err_get_macaddr:
+-err_alloc_ctrl_buf:
+       kfree(dev->ctrl_buf);
++err_alloc_ctrl_buf:
+ err_endpoints:
+       free_netdev(netdev);
+       return retval;
diff --git a/target/linux/generic/backport-5.15/796-v6.5-02-usbnet-ipheth-transmit-URBs-without-trailing-padding.patch b/target/linux/generic/backport-5.15/796-v6.5-02-usbnet-ipheth-transmit-URBs-without-trailing-padding.patch
new file mode 100644 (file)
index 0000000..adfec35
--- /dev/null
@@ -0,0 +1,35 @@
+From 3e65efcca87a9bb5f3b864e0a43d167bc0a8688c Mon Sep 17 00:00:00 2001
+From: Foster Snowhill <forst@pen.gy>
+Date: Wed, 7 Jun 2023 15:57:00 +0200
+Subject: [PATCH 2/4] usbnet: ipheth: transmit URBs without trailing padding
+
+The behaviour of the official iOS tethering driver on macOS is to not
+transmit any trailing padding at the end of URBs. This is applicable
+to both NCM and legacy modes, including older devices.
+
+Adapt the driver to not include trailing padding in TX URBs, matching
+the behaviour of the official macOS driver.
+
+Signed-off-by: Foster Snowhill <forst@pen.gy>
+Tested-by: Georgi Valkov <gvalkov@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/usb/ipheth.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+--- a/drivers/net/usb/ipheth.c
++++ b/drivers/net/usb/ipheth.c
+@@ -373,12 +373,10 @@ static netdev_tx_t ipheth_tx(struct sk_b
+       }
+       memcpy(dev->tx_buf, skb->data, skb->len);
+-      if (skb->len < IPHETH_BUF_SIZE)
+-              memset(dev->tx_buf + skb->len, 0, IPHETH_BUF_SIZE - skb->len);
+       usb_fill_bulk_urb(dev->tx_urb, udev,
+                         usb_sndbulkpipe(udev, dev->bulk_out),
+-                        dev->tx_buf, IPHETH_BUF_SIZE,
++                        dev->tx_buf, skb->len,
+                         ipheth_sndbulk_callback,
+                         dev);
+       dev->tx_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
diff --git a/target/linux/generic/backport-5.15/796-v6.5-03-usbnet-ipheth-add-CDC-NCM-support.patch b/target/linux/generic/backport-5.15/796-v6.5-03-usbnet-ipheth-add-CDC-NCM-support.patch
new file mode 100644 (file)
index 0000000..e3f2b9c
--- /dev/null
@@ -0,0 +1,326 @@
+From a2d274c62e44b1995c170595db3865c6fe701226 Mon Sep 17 00:00:00 2001
+From: Foster Snowhill <forst@pen.gy>
+Date: Wed, 7 Jun 2023 15:57:01 +0200
+Subject: [PATCH 3/4] usbnet: ipheth: add CDC NCM support
+
+Recent iOS releases support CDC NCM encapsulation on RX. This mode is
+the default on macOS and Windows. In this mode, an iOS device may include
+one or more Ethernet frames inside a single URB.
+
+Freshly booted iOS devices start in legacy mode, but are put into
+NCM mode by the official Apple driver. When reconnecting such a device
+from a macOS/Windows machine to a Linux host, the device stays in
+NCM mode, making it unusable with the legacy ipheth driver code.
+
+To correctly support such a device, the driver has to either support
+the NCM mode too, or put the device back into legacy mode.
+
+To match the behaviour of the macOS/Windows driver, and since there
+is no documented control command to revert to legacy mode, implement
+NCM support. The device is attempted to be put into NCM mode by default,
+and falls back to legacy mode if the attempt fails.
+
+Signed-off-by: Foster Snowhill <forst@pen.gy>
+Tested-by: Georgi Valkov <gvalkov@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/usb/ipheth.c | 180 +++++++++++++++++++++++++++++++++------
+ 1 file changed, 155 insertions(+), 25 deletions(-)
+
+--- a/drivers/net/usb/ipheth.c
++++ b/drivers/net/usb/ipheth.c
+@@ -52,6 +52,7 @@
+ #include <linux/ethtool.h>
+ #include <linux/usb.h>
+ #include <linux/workqueue.h>
++#include <linux/usb/cdc.h>
+ #define USB_VENDOR_APPLE        0x05ac
+@@ -59,8 +60,12 @@
+ #define IPHETH_USBINTF_SUBCLASS 253
+ #define IPHETH_USBINTF_PROTO    1
+-#define IPHETH_BUF_SIZE         1514
+ #define IPHETH_IP_ALIGN               2       /* padding at front of URB */
++#define IPHETH_NCM_HEADER_SIZE  (12 + 96) /* NCMH + NCM0 */
++#define IPHETH_TX_BUF_SIZE      ETH_FRAME_LEN
++#define IPHETH_RX_BUF_SIZE_LEGACY (IPHETH_IP_ALIGN + ETH_FRAME_LEN)
++#define IPHETH_RX_BUF_SIZE_NCM        65536
++
+ #define IPHETH_TX_TIMEOUT       (5 * HZ)
+ #define IPHETH_INTFNUM          2
+@@ -71,6 +76,7 @@
+ #define IPHETH_CTRL_TIMEOUT     (5 * HZ)
+ #define IPHETH_CMD_GET_MACADDR   0x00
++#define IPHETH_CMD_ENABLE_NCM    0x04
+ #define IPHETH_CMD_CARRIER_CHECK 0x45
+ #define IPHETH_CARRIER_CHECK_TIMEOUT round_jiffies_relative(1 * HZ)
+@@ -97,6 +103,8 @@ struct ipheth_device {
+       u8 bulk_out;
+       struct delayed_work carrier_work;
+       bool confirmed_pairing;
++      int (*rcvbulk_callback)(struct urb *urb);
++      size_t rx_buf_len;
+ };
+ static int ipheth_rx_submit(struct ipheth_device *dev, gfp_t mem_flags);
+@@ -116,12 +124,12 @@ static int ipheth_alloc_urbs(struct iphe
+       if (rx_urb == NULL)
+               goto free_tx_urb;
+-      tx_buf = usb_alloc_coherent(iphone->udev, IPHETH_BUF_SIZE,
++      tx_buf = usb_alloc_coherent(iphone->udev, IPHETH_TX_BUF_SIZE,
+                                   GFP_KERNEL, &tx_urb->transfer_dma);
+       if (tx_buf == NULL)
+               goto free_rx_urb;
+-      rx_buf = usb_alloc_coherent(iphone->udev, IPHETH_BUF_SIZE + IPHETH_IP_ALIGN,
++      rx_buf = usb_alloc_coherent(iphone->udev, iphone->rx_buf_len,
+                                   GFP_KERNEL, &rx_urb->transfer_dma);
+       if (rx_buf == NULL)
+               goto free_tx_buf;
+@@ -134,7 +142,7 @@ static int ipheth_alloc_urbs(struct iphe
+       return 0;
+ free_tx_buf:
+-      usb_free_coherent(iphone->udev, IPHETH_BUF_SIZE, tx_buf,
++      usb_free_coherent(iphone->udev, IPHETH_TX_BUF_SIZE, tx_buf,
+                         tx_urb->transfer_dma);
+ free_rx_urb:
+       usb_free_urb(rx_urb);
+@@ -146,9 +154,9 @@ error_nomem:
+ static void ipheth_free_urbs(struct ipheth_device *iphone)
+ {
+-      usb_free_coherent(iphone->udev, IPHETH_BUF_SIZE + IPHETH_IP_ALIGN, iphone->rx_buf,
++      usb_free_coherent(iphone->udev, iphone->rx_buf_len, iphone->rx_buf,
+                         iphone->rx_urb->transfer_dma);
+-      usb_free_coherent(iphone->udev, IPHETH_BUF_SIZE, iphone->tx_buf,
++      usb_free_coherent(iphone->udev, IPHETH_TX_BUF_SIZE, iphone->tx_buf,
+                         iphone->tx_urb->transfer_dma);
+       usb_free_urb(iphone->rx_urb);
+       usb_free_urb(iphone->tx_urb);
+@@ -160,15 +168,106 @@ static void ipheth_kill_urbs(struct iphe
+       usb_kill_urb(dev->rx_urb);
+ }
+-static void ipheth_rcvbulk_callback(struct urb *urb)
++static int ipheth_consume_skb(char *buf, int len, struct ipheth_device *dev)
+ {
+-      struct ipheth_device *dev;
+       struct sk_buff *skb;
+-      int status;
++
++      skb = dev_alloc_skb(len);
++      if (!skb) {
++              dev->net->stats.rx_dropped++;
++              return -ENOMEM;
++      }
++
++      skb_put_data(skb, buf, len);
++      skb->dev = dev->net;
++      skb->protocol = eth_type_trans(skb, dev->net);
++
++      dev->net->stats.rx_packets++;
++      dev->net->stats.rx_bytes += len;
++      netif_rx(skb);
++
++      return 0;
++}
++
++static int ipheth_rcvbulk_callback_legacy(struct urb *urb)
++{
++      struct ipheth_device *dev;
++      char *buf;
++      int len;
++
++      dev = urb->context;
++
++      if (urb->actual_length <= IPHETH_IP_ALIGN) {
++              dev->net->stats.rx_length_errors++;
++              return -EINVAL;
++      }
++      len = urb->actual_length - IPHETH_IP_ALIGN;
++      buf = urb->transfer_buffer + IPHETH_IP_ALIGN;
++
++      return ipheth_consume_skb(buf, len, dev);
++}
++
++static int ipheth_rcvbulk_callback_ncm(struct urb *urb)
++{
++      struct usb_cdc_ncm_nth16 *ncmh;
++      struct usb_cdc_ncm_ndp16 *ncm0;
++      struct usb_cdc_ncm_dpe16 *dpe;
++      struct ipheth_device *dev;
++      int retval = -EINVAL;
+       char *buf;
+       int len;
+       dev = urb->context;
++
++      if (urb->actual_length < IPHETH_NCM_HEADER_SIZE) {
++              dev->net->stats.rx_length_errors++;
++              return retval;
++      }
++
++      ncmh = urb->transfer_buffer;
++      if (ncmh->dwSignature != cpu_to_le32(USB_CDC_NCM_NTH16_SIGN) ||
++          le16_to_cpu(ncmh->wNdpIndex) >= urb->actual_length) {
++              dev->net->stats.rx_errors++;
++              return retval;
++      }
++
++      ncm0 = urb->transfer_buffer + le16_to_cpu(ncmh->wNdpIndex);
++      if (ncm0->dwSignature != cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN) ||
++          le16_to_cpu(ncmh->wHeaderLength) + le16_to_cpu(ncm0->wLength) >=
++          urb->actual_length) {
++              dev->net->stats.rx_errors++;
++              return retval;
++      }
++
++      dpe = ncm0->dpe16;
++      while (le16_to_cpu(dpe->wDatagramIndex) != 0 &&
++             le16_to_cpu(dpe->wDatagramLength) != 0) {
++              if (le16_to_cpu(dpe->wDatagramIndex) >= urb->actual_length ||
++                  le16_to_cpu(dpe->wDatagramIndex) +
++                  le16_to_cpu(dpe->wDatagramLength) > urb->actual_length) {
++                      dev->net->stats.rx_length_errors++;
++                      return retval;
++              }
++
++              buf = urb->transfer_buffer + le16_to_cpu(dpe->wDatagramIndex);
++              len = le16_to_cpu(dpe->wDatagramLength);
++
++              retval = ipheth_consume_skb(buf, len, dev);
++              if (retval != 0)
++                      return retval;
++
++              dpe++;
++      }
++
++      return 0;
++}
++
++static void ipheth_rcvbulk_callback(struct urb *urb)
++{
++      struct ipheth_device *dev;
++      int retval, status;
++
++      dev = urb->context;
+       if (dev == NULL)
+               return;
+@@ -191,25 +290,27 @@ static void ipheth_rcvbulk_callback(stru
+               dev->net->stats.rx_length_errors++;
+               return;
+       }
+-      len = urb->actual_length - IPHETH_IP_ALIGN;
+-      buf = urb->transfer_buffer + IPHETH_IP_ALIGN;
+-      skb = dev_alloc_skb(len);
+-      if (!skb) {
+-              dev_err(&dev->intf->dev, "%s: dev_alloc_skb: -ENOMEM\n",
+-                      __func__);
+-              dev->net->stats.rx_dropped++;
++      /* RX URBs starting with 0x00 0x01 do not encapsulate Ethernet frames,
++       * but rather are control frames. Their purpose is not documented, and
++       * they don't affect driver functionality, okay to drop them.
++       * There is usually just one 4-byte control frame as the very first
++       * URB received from the bulk IN endpoint.
++       */
++      if (unlikely
++              (((char *)urb->transfer_buffer)[0] == 0 &&
++               ((char *)urb->transfer_buffer)[1] == 1))
++              goto rx_submit;
++
++      retval = dev->rcvbulk_callback(urb);
++      if (retval != 0) {
++              dev_err(&dev->intf->dev, "%s: callback retval: %d\n",
++                      __func__, retval);
+               return;
+       }
+-      skb_put_data(skb, buf, len);
+-      skb->dev = dev->net;
+-      skb->protocol = eth_type_trans(skb, dev->net);
+-
+-      dev->net->stats.rx_packets++;
+-      dev->net->stats.rx_bytes += len;
++rx_submit:
+       dev->confirmed_pairing = true;
+-      netif_rx(skb);
+       ipheth_rx_submit(dev, GFP_ATOMIC);
+ }
+@@ -310,6 +411,27 @@ static int ipheth_get_macaddr(struct iph
+       return retval;
+ }
++static int ipheth_enable_ncm(struct ipheth_device *dev)
++{
++      struct usb_device *udev = dev->udev;
++      int retval;
++
++      retval = usb_control_msg(udev,
++                               usb_sndctrlpipe(udev, IPHETH_CTRL_ENDP),
++                               IPHETH_CMD_ENABLE_NCM, /* request */
++                               0x41, /* request type */
++                               0x00, /* value */
++                               0x02, /* index */
++                               NULL,
++                               0,
++                               IPHETH_CTRL_TIMEOUT);
++
++      dev_info(&dev->intf->dev, "%s: usb_control_msg: %d\n",
++               __func__, retval);
++
++      return retval;
++}
++
+ static int ipheth_rx_submit(struct ipheth_device *dev, gfp_t mem_flags)
+ {
+       struct usb_device *udev = dev->udev;
+@@ -317,7 +439,7 @@ static int ipheth_rx_submit(struct iphet
+       usb_fill_bulk_urb(dev->rx_urb, udev,
+                         usb_rcvbulkpipe(udev, dev->bulk_in),
+-                        dev->rx_buf, IPHETH_BUF_SIZE + IPHETH_IP_ALIGN,
++                        dev->rx_buf, dev->rx_buf_len,
+                         ipheth_rcvbulk_callback,
+                         dev);
+       dev->rx_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+@@ -365,7 +487,7 @@ static netdev_tx_t ipheth_tx(struct sk_b
+       int retval;
+       /* Paranoid */
+-      if (skb->len > IPHETH_BUF_SIZE) {
++      if (skb->len > IPHETH_TX_BUF_SIZE) {
+               WARN(1, "%s: skb too large: %d bytes\n", __func__, skb->len);
+               dev->net->stats.tx_dropped++;
+               dev_kfree_skb_any(skb);
+@@ -448,6 +570,8 @@ static int ipheth_probe(struct usb_inter
+       dev->net = netdev;
+       dev->intf = intf;
+       dev->confirmed_pairing = false;
++      dev->rx_buf_len = IPHETH_RX_BUF_SIZE_LEGACY;
++      dev->rcvbulk_callback = ipheth_rcvbulk_callback_legacy;
+       /* Set up endpoints */
+       hintf = usb_altnum_to_altsetting(intf, IPHETH_ALT_INTFNUM);
+       if (hintf == NULL) {
+@@ -479,6 +603,12 @@ static int ipheth_probe(struct usb_inter
+       if (retval)
+               goto err_get_macaddr;
++      retval = ipheth_enable_ncm(dev);
++      if (!retval) {
++              dev->rx_buf_len = IPHETH_RX_BUF_SIZE_NCM;
++              dev->rcvbulk_callback = ipheth_rcvbulk_callback_ncm;
++      }
++
+       INIT_DELAYED_WORK(&dev->carrier_work, ipheth_carrier_check_work);
+       retval = ipheth_alloc_urbs(dev);
diff --git a/target/linux/generic/backport-5.15/796-v6.5-04-usbnet-ipheth-update-Kconfig-description.patch b/target/linux/generic/backport-5.15/796-v6.5-04-usbnet-ipheth-update-Kconfig-description.patch
new file mode 100644 (file)
index 0000000..2ab7e8f
--- /dev/null
@@ -0,0 +1,36 @@
+From 0c6e9d32ef0ccfcf2d875cbcff23bf345a54d585 Mon Sep 17 00:00:00 2001
+From: Foster Snowhill <forst@pen.gy>
+Date: Wed, 7 Jun 2023 15:57:02 +0200
+Subject: [PATCH 4/4] usbnet: ipheth: update Kconfig description
+
+This module has for a long time not been limited to iPhone <= 3GS.
+Update description to match the actual state of the driver.
+
+Remove dead link from 2010, instead reference an existing userspace
+iOS device pairing implementation as part of libimobiledevice.
+
+Signed-off-by: Foster Snowhill <forst@pen.gy>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/usb/Kconfig | 10 ++++------
+ 1 file changed, 4 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/usb/Kconfig
++++ b/drivers/net/usb/Kconfig
+@@ -582,12 +582,10 @@ config USB_IPHETH
+       default n
+       help
+         Module used to share Internet connection (tethering) from your
+-        iPhone (Original, 3G and 3GS) to your system.
+-        Note that you need userspace libraries and programs that are needed
+-        to pair your device with your system and that understand the iPhone
+-        protocol.
+-
+-        For more information: http://giagio.com/wiki/moin.cgi/iPhoneEthernetDriver
++        iPhone to your system.
++        Note that you need a corresponding userspace library/program
++        to pair your device with your system, for example usbmuxd
++        <https://github.com/libimobiledevice/usbmuxd>.
+ config USB_SIERRA_NET
+       tristate "USB-to-WWAN Driver for Sierra Wireless modems"