net: cdc_mbim: handle IPv6 Neigbor Solicitations
authorBjørn Mork <bjorn@mork.no>
Thu, 31 Oct 2013 14:56:10 +0000 (15:56 +0100)
committerDavid S. Miller <davem@davemloft.net>
Sat, 2 Nov 2013 05:29:37 +0000 (01:29 -0400)
MBIM is a point-to-point protocol transporting raw IP packets
with no L2 headers. Only IPv4 and IPv6 are supported. ARP in
particular is not, which is quite logical given the lack of
L2 headers.

The driver still emulates an ethernet interface, dropping all
unsupported protocols, and avoiding neigbour resolving by
setting the IFF_NOARP flag.

The MBIM specification does not explicitly forbid IPv6 Neighbor
Discovery, and it seems the other OS support will respond to
Neighbor Solicitations on MBIM links. There are therefore
buggy devices out there, which despite the pointlessness, still
require Neighbor Discovery for IPv6 over MBIM.

This is incompatible with the IFF_NOARP flag which disables
both ARP and ND.  We cannot support ARP in any case, so we
have to keep that flag. This patch implements a workaround
for the buggy devices, letting the driver respond directly
to Neighbor Solicitations from the device.

This is not optimal, but will have minimal effect on any sane
device.

Cc: Greg Suarez <gsuarez@smithmicro.com>
Reported-and-tested-by: Thomas Schäfer <tschaefer@t-online.de>
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 25ba7ec..6533a9f 100644 (file)
@@ -21,6 +21,8 @@
 #include <linux/usb/usbnet.h>
 #include <linux/usb/cdc-wdm.h>
 #include <linux/usb/cdc_ncm.h>
+#include <net/ipv6.h>
+#include <net/addrconf.h>
 
 /* driver specific data - must match cdc_ncm usage */
 struct cdc_mbim_state {
@@ -184,6 +186,60 @@ error:
        return NULL;
 }
 
+/* Some devices are known to send Neigbor Solicitation messages and
+ * require Neigbor Advertisement replies.  The IPv6 core will not
+ * respond since IFF_NOARP is set, so we must handle them ourselves.
+ */
+static void do_neigh_solicit(struct usbnet *dev, u8 *buf, u16 tci)
+{
+       struct ipv6hdr *iph = (void *)buf;
+       struct nd_msg *msg = (void *)(iph + 1);
+       struct net_device *netdev;
+       struct inet6_dev *in6_dev;
+       bool is_router;
+
+       /* we'll only respond to requests from unicast addresses to
+        * our solicited node addresses.
+        */
+       if (!ipv6_addr_is_solict_mult(&iph->daddr) ||
+           !(ipv6_addr_type(&iph->saddr) & IPV6_ADDR_UNICAST))
+               return;
+
+       /* need to send the NA on the VLAN dev, if any */
+       if (tci)
+               netdev = __vlan_find_dev_deep(dev->net, htons(ETH_P_8021Q),
+                                             tci);
+       else
+               netdev = dev->net;
+       if (!netdev)
+               return;
+
+       in6_dev = in6_dev_get(netdev);
+       if (!in6_dev)
+               return;
+       is_router = !!in6_dev->cnf.forwarding;
+       in6_dev_put(in6_dev);
+
+       /* ipv6_stub != NULL if in6_dev_get returned an inet6_dev */
+       ipv6_stub->ndisc_send_na(netdev, NULL, &iph->saddr, &msg->target,
+                                is_router /* router */,
+                                true /* solicited */,
+                                false /* override */,
+                                true /* inc_opt */);
+}
+
+static bool is_neigh_solicit(u8 *buf, size_t len)
+{
+       struct ipv6hdr *iph = (void *)buf;
+       struct nd_msg *msg = (void *)(iph + 1);
+
+       return (len >= sizeof(struct ipv6hdr) + sizeof(struct nd_msg) &&
+               iph->nexthdr == IPPROTO_ICMPV6 &&
+               msg->icmph.icmp6_code == 0 &&
+               msg->icmph.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION);
+}
+
+
 static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len, u16 tci)
 {
        __be16 proto = htons(ETH_P_802_3);
@@ -198,6 +254,8 @@ static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_
                        proto = htons(ETH_P_IP);
                        break;
                case 0x60:
+                       if (is_neigh_solicit(buf, len))
+                               do_neigh_solicit(dev, buf, tci);
                        proto = htons(ETH_P_IPV6);
                        break;
                default: