net: cdc_mbim: optionally use VLAN ID 4094 for IP session 0
authorBjørn Mork <bjorn@mork.no>
Sun, 11 May 2014 08:47:12 +0000 (10:47 +0200)
committerDavid S. Miller <davem@davemloft.net>
Tue, 13 May 2014 21:46:09 +0000 (17:46 -0400)
The cdc_mbim driver maps 802.1q VLANs to MBIM IP and DSS
sessions. MBIM IP session 0 is handled as an exception and
is mapped to untagged frames.

This patch adds optional support for remapping MBIM IP
session 0 to 802.1q VLAN ID 4094 instead. The default
behaviour is not changed. The new behaviour is triggered
by adding a link for this previously unsupported VLAN.

The untagged mapping was chosen initially to support the
assumed most common use case: Most current MBIM devices only
support a single IP session (i.e. session 0 only), and using
untagged frames lets the users completely ignore the
additonal complexity of the multiplexing layer.

But when the multiplexing features of MBIM are used, then
this netdev gets a double meaning: It becomes the master
interface for all the VLAN subdevs the additional sessions
are mapped to, while still serving as the untagged IP
interface for session 0.

This can be problematic, especially when using Device Service
Streams (DSS), as have become apparent recently with the
availability of devices with real DSS support. Some use cases
need to e.g set a MTU which is higher than allowed for IP
Session 0. The dual role also leads to the situation where
the IP Session 0 interface cannot be taken down without
breaking unrelated IP or DSS sessions - a devastating side
effect which applications managing a simple IP session cannot
be expected to be aware of. A typical DHCP client will assume
that it should bring the interface down after releasing the
IP lease.

These problems can be avoided by tagging IP session 0 packets
too, making this session similar to all other multiplexed
sessions. This redefines the main netdev as an upper master
interface only.

Cc: Greg Suarez <gsuarez@smithmicro.com>
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/usb/cdc_mbim.c

index 0ab79fc..694a879 100644 (file)
 #include <net/ipv6.h>
 #include <net/addrconf.h>
 
+/* alternative VLAN for IP session 0 if not untagged */
+#define MBIM_IPS0_VID  4094
+
 /* driver specific data - must match cdc_ncm usage */
 struct cdc_mbim_state {
        struct cdc_ncm_ctx *ctx;
        atomic_t pmcount;
        struct usb_driver *subdriver;
-       struct usb_interface *control;
-       struct usb_interface *data;
+       unsigned long _unused;
+       unsigned long flags;
+};
+
+/* flags for the cdc_mbim_state.flags field */
+enum cdc_mbim_flags {
+       FLAG_IPS0_VLAN = 1 << 0,        /* IP session 0 is tagged  */
 };
 
 /* using a counter to merge subdriver requests with our own into a combined state */
@@ -62,6 +70,42 @@ static int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status)
        return cdc_mbim_manage_power(dev, status);
 }
 
+static int cdc_mbim_rx_add_vid(struct net_device *netdev, __be16 proto, u16 vid)
+{
+       struct usbnet *dev = netdev_priv(netdev);
+       struct cdc_mbim_state *info = (void *)&dev->data;
+
+       /* creation of this VLAN is a request to tag IP session 0 */
+       if (vid == MBIM_IPS0_VID)
+               info->flags |= FLAG_IPS0_VLAN;
+       else
+               if (vid >= 512) /* we don't map these to MBIM session */
+                       return -EINVAL;
+       return 0;
+}
+
+static int cdc_mbim_rx_kill_vid(struct net_device *netdev, __be16 proto, u16 vid)
+{
+       struct usbnet *dev = netdev_priv(netdev);
+       struct cdc_mbim_state *info = (void *)&dev->data;
+
+       /* this is a request for an untagged IP session 0 */
+       if (vid == MBIM_IPS0_VID)
+               info->flags &= ~FLAG_IPS0_VLAN;
+       return 0;
+}
+
+static const struct net_device_ops cdc_mbim_netdev_ops = {
+       .ndo_open             = usbnet_open,
+       .ndo_stop             = usbnet_stop,
+       .ndo_start_xmit       = usbnet_start_xmit,
+       .ndo_tx_timeout       = usbnet_tx_timeout,
+       .ndo_change_mtu       = usbnet_change_mtu,
+       .ndo_set_mac_address  = eth_mac_addr,
+       .ndo_validate_addr    = eth_validate_addr,
+       .ndo_vlan_rx_add_vid  = cdc_mbim_rx_add_vid,
+       .ndo_vlan_rx_kill_vid = cdc_mbim_rx_kill_vid,
+};
 
 static int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf)
 {
@@ -101,7 +145,10 @@ static int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf)
        dev->net->flags |= IFF_NOARP;
 
        /* no need to put the VLAN tci in the packet headers */
-       dev->net->features |= NETIF_F_HW_VLAN_CTAG_TX;
+       dev->net->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_FILTER;
+
+       /* monitor VLAN additions and removals */
+       dev->net->netdev_ops = &cdc_mbim_netdev_ops;
 err:
        return ret;
 }
@@ -164,12 +211,24 @@ static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb
                        skb_pull(skb, ETH_HLEN);
                }
 
+               /* Is IP session <0> tagged too? */
+               if (info->flags & FLAG_IPS0_VLAN) {
+                       /* drop all untagged packets */
+                       if (!tci)
+                               goto error;
+                       /* map MBIM_IPS0_VID to IPS<0> */
+                       if (tci == MBIM_IPS0_VID)
+                               tci = 0;
+               }
+
                /* mapping VLANs to MBIM sessions:
-                *   no tag     => IPS session <0>
+                *   no tag     => IPS session <0> if !FLAG_IPS0_VLAN
                 *   1 - 255    => IPS session <vlanid>
                 *   256 - 511  => DSS session <vlanid - 256>
-                *   512 - 4095 => unsupported, drop
+                *   512 - 4093 => unsupported, drop
+                *   4094       => IPS session <0> if FLAG_IPS0_VLAN
                 */
+
                switch (tci & 0x0f00) {
                case 0x0000: /* VLAN ID 0 - 255 */
                        if (!is_ip)
@@ -268,7 +327,7 @@ static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_
        __be16 proto = htons(ETH_P_802_3);
        struct sk_buff *skb = NULL;
 
-       if (tci < 256) { /* IPS session? */
+       if (tci < 256 || tci == MBIM_IPS0_VID) { /* IPS session? */
                if (len < sizeof(struct iphdr))
                        goto err;
 
@@ -338,6 +397,9 @@ next_ndp:
        case cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN):
                c = (u8 *)&ndp16->dwSignature;
                tci = c[3];
+               /* tag IPS<0> packets too if MBIM_IPS0_VID exists */
+               if (!tci && info->flags & FLAG_IPS0_VLAN)
+                       tci = MBIM_IPS0_VID;
                break;
        case cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN):
                c = (u8 *)&ndp16->dwSignature;