[ATM]: [br2864] routed support
[linux-2.6-block.git] / net / atm / br2684.c
index c0f6861eefe35061061d7ae39d4ecde87d8777da..d9bb2a16b7cbfcff2c0cb41d833a5484a2812b33 100644 (file)
@@ -1,7 +1,8 @@
 /*
-Experimental ethernet netdevice using ATM AAL5 as underlying carrier
-(RFC1483 obsoleted by RFC2684) for Linux 2.4
-Author: Marcell GAL, 2000, XDSL Ltd, Hungary
+Ethernet netdevice using ATM AAL5 as underlying carrier
+(RFC1483 obsoleted by RFC2684) for Linux
+Authors: Marcell GAL, 2000, XDSL Ltd, Hungary
+         Eric Kinzie, 2006-2007, US Naval Research Laboratory
 */
 
 #include <linux/module.h>
@@ -24,22 +25,6 @@ Author: Marcell GAL, 2000, XDSL Ltd, Hungary
 
 #include "common.h"
 
-/*
- * Define this to use a version of the code which interacts with the higher
- * layers in a more intellegent way, by always reserving enough space for
- * our header at the begining of the packet.  However, there may still be
- * some problems with programs like tcpdump.  In 2.5 we'll sort out what
- * we need to do to get this perfect.  For now we just will copy the packet
- * if we need space for the header
- */
-/* #define FASTER_VERSION */
-
-#ifdef DEBUG
-#define DPRINTK(format, args...) printk(KERN_DEBUG "br2684: " format, ##args)
-#else
-#define DPRINTK(format, args...)
-#endif
-
 #ifdef SKB_DEBUG
 static void skb_debug(const struct sk_buff *skb)
 {
@@ -55,9 +40,27 @@ static void skb_debug(const struct sk_buff *skb)
 #define skb_debug(skb) do {} while (0)
 #endif
 
+#define BR2684_ETHERTYPE_LEN   2
+#define BR2684_PAD_LEN         2
+
+#define LLC            0xaa, 0xaa, 0x03
+#define SNAP_BRIDGED   0x00, 0x80, 0xc2
+#define SNAP_ROUTED    0x00, 0x00, 0x00
+#define PID_ETHERNET   0x00, 0x07
+#define ETHERTYPE_IPV4 0x08, 0x00
+#define ETHERTYPE_IPV6 0x86, 0xdd
+#define PAD_BRIDGED    0x00, 0x00
+
+static unsigned char ethertype_ipv4[] =
+       { ETHERTYPE_IPV4 };
+static unsigned char ethertype_ipv6[] =
+       { ETHERTYPE_IPV6 };
 static unsigned char llc_oui_pid_pad[] =
-    { 0xAA, 0xAA, 0x03, 0x00, 0x80, 0xC2, 0x00, 0x07, 0x00, 0x00 };
-#define PADLEN (2)
+       { LLC, SNAP_BRIDGED, PID_ETHERNET, PAD_BRIDGED };
+static unsigned char llc_oui_ipv4[] =
+       { LLC, SNAP_ROUTED, ETHERTYPE_IPV4 };
+static unsigned char llc_oui_ipv6[] =
+       { LLC, SNAP_ROUTED, ETHERTYPE_IPV6 };
 
 enum br2684_encaps {
        e_vc  = BR2684_ENCAPS_VC,
@@ -75,9 +78,7 @@ struct br2684_vcc {
 #ifdef CONFIG_ATM_BR2684_IPFILTER
        struct br2684_filter filter;
 #endif /* CONFIG_ATM_BR2684_IPFILTER */
-#ifndef FASTER_VERSION
        unsigned copies_needed, copies_failed;
-#endif /* FASTER_VERSION */
 };
 
 struct br2684_dev {
@@ -87,6 +88,7 @@ struct br2684_dev {
        struct list_head brvccs; /* one device <=> one vcc (before xmas) */
        struct net_device_stats stats;
        int mac_was_set;
+       enum br2684_payload payload;
 };
 
 /*
@@ -153,14 +155,8 @@ static int br2684_xmit_vcc(struct sk_buff *skb, struct br2684_dev *brdev,
        struct br2684_vcc *brvcc)
 {
        struct atm_vcc *atmvcc;
-#ifdef FASTER_VERSION
-       if (brvcc->encaps == e_llc)
-               memcpy(skb_push(skb, 8), llc_oui_pid_pad, 8);
-       /* last 2 bytes of llc_oui_pid_pad are managed by header routines;
-          yes, you got it: 8 + 2 = sizeof(llc_oui_pid_pad)
-        */
-#else
        int minheadroom = (brvcc->encaps == e_llc) ? 10 : 2;
+
        if (skb_headroom(skb) < minheadroom) {
                struct sk_buff *skb2 = skb_realloc_headroom(skb, minheadroom);
                brvcc->copies_needed++;
@@ -171,16 +167,36 @@ static int br2684_xmit_vcc(struct sk_buff *skb, struct br2684_dev *brdev,
                }
                skb = skb2;
        }
-       skb_push(skb, minheadroom);
-       if (brvcc->encaps == e_llc)
-               skb_copy_to_linear_data(skb, llc_oui_pid_pad, 10);
-       else
-               memset(skb->data, 0, 2);
-#endif /* FASTER_VERSION */
+
+       if (brvcc->encaps == e_llc) {
+               if (brdev->payload == p_bridged) {
+                       skb_push(skb, sizeof(llc_oui_pid_pad));
+                       skb_copy_to_linear_data(skb, llc_oui_pid_pad, sizeof(llc_oui_pid_pad));
+               } else if (brdev->payload == p_routed) {
+                       unsigned short prot = ntohs(skb->protocol);
+
+                       skb_push(skb, sizeof(llc_oui_ipv4));
+                       switch (prot) {
+                               case ETH_P_IP:
+                                       skb_copy_to_linear_data(skb, llc_oui_ipv4, sizeof(llc_oui_ipv4));
+                                       break;
+                               case ETH_P_IPV6:
+                                       skb_copy_to_linear_data(skb, llc_oui_ipv6, sizeof(llc_oui_ipv6));
+                                       break;
+                               default:
+                                       dev_kfree_skb(skb);
+                                       return 0;
+                       }
+               }
+       } else {
+               skb_push(skb, 2);
+               if (brdev->payload == p_bridged)
+                       memset(skb->data, 0, 2);
+       }
        skb_debug(skb);
 
        ATM_SKB(skb)->vcc = atmvcc = brvcc->atmvcc;
-       DPRINTK("atm_skb(%p)->vcc(%p)->dev(%p)\n", skb, atmvcc, atmvcc->dev);
+       pr_debug("atm_skb(%p)->vcc(%p)->dev(%p)\n", skb, atmvcc, atmvcc->dev);
        if (!atm_may_send(atmvcc, skb->truesize)) {
                /* we free this here for now, because we cannot know in a higher
                        layer whether the skb point it supplied wasn't freed yet.
@@ -209,11 +225,11 @@ static int br2684_start_xmit(struct sk_buff *skb, struct net_device *dev)
        struct br2684_dev *brdev = BRPRIV(dev);
        struct br2684_vcc *brvcc;
 
-       DPRINTK("br2684_start_xmit, skb->dst=%p\n", skb->dst);
+       pr_debug("br2684_start_xmit, skb->dst=%p\n", skb->dst);
        read_lock(&devs_lock);
        brvcc = pick_outgoing_vcc(skb, brdev);
        if (brvcc == NULL) {
-               DPRINTK("no vcc attached to dev %s\n", dev->name);
+               pr_debug("no vcc attached to dev %s\n", dev->name);
                brdev->stats.tx_errors++;
                brdev->stats.tx_carrier_errors++;
                /* netif_stop_queue(dev); */
@@ -239,91 +255,10 @@ static int br2684_start_xmit(struct sk_buff *skb, struct net_device *dev)
 
 static struct net_device_stats *br2684_get_stats(struct net_device *dev)
 {
-       DPRINTK("br2684_get_stats\n");
+       pr_debug("br2684_get_stats\n");
        return &BRPRIV(dev)->stats;
 }
 
-#ifdef FASTER_VERSION
-/*
- * These mirror eth_header and eth_header_cache.  They are not usually
- * exported for use in modules, so we grab them from net_device
- * after ether_setup() is done with it.  Bit of a hack.
- */
-static int (*my_eth_header)(struct sk_buff *, struct net_device *,
-       unsigned short, void *, void *, unsigned);
-static int (*my_eth_header_cache)(struct neighbour *, struct hh_cache *);
-
-static int
-br2684_header(struct sk_buff *skb, struct net_device *dev,
-             unsigned short type, void *daddr, void *saddr, unsigned len)
-{
-       u16 *pad_before_eth;
-       int t = my_eth_header(skb, dev, type, daddr, saddr, len);
-       if (t > 0) {
-               pad_before_eth = (u16 *) skb_push(skb, 2);
-               *pad_before_eth = 0;
-               return dev->hard_header_len;    /* or return 16; ? */
-       } else
-               return t;
-}
-
-static int
-br2684_header_cache(struct neighbour *neigh, struct hh_cache *hh)
-{
-/* hh_data is 16 bytes long. if encaps is ether-llc we need 24, so
-xmit will add the additional header part in that case */
-       u16 *pad_before_eth = (u16 *)(hh->hh_data);
-       int t = my_eth_header_cache(neigh, hh);
-       DPRINTK("br2684_header_cache, neigh=%p, hh_cache=%p\n", neigh, hh);
-       if (t < 0)
-               return t;
-       else {
-               *pad_before_eth = 0;
-               hh->hh_len = PADLEN + ETH_HLEN;
-       }
-       return 0;
-}
-
-/*
- * This is similar to eth_type_trans, which cannot be used because of
- * our dev->hard_header_len
- */
-static inline __be16 br_type_trans(struct sk_buff *skb, struct net_device *dev)
-{
-       struct ethhdr *eth;
-       unsigned char *rawp;
-       eth = eth_hdr(skb);
-
-       if (is_multicast_ether_addr(eth->h_dest)) {
-               if (!compare_ether_addr(eth->h_dest, dev->broadcast))
-                       skb->pkt_type = PACKET_BROADCAST;
-               else
-                       skb->pkt_type = PACKET_MULTICAST;
-       }
-
-       else if (compare_ether_addr(eth->h_dest, dev->dev_addr))
-               skb->pkt_type = PACKET_OTHERHOST;
-
-       if (ntohs(eth->h_proto) >= 1536)
-               return eth->h_proto;
-
-       rawp = skb->data;
-
-       /*
-        * This is a magic hack to spot IPX packets. Older Novell breaks
-        * the protocol design and runs IPX over 802.3 without an 802.2 LLC
-        * layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This
-        * won't work for fault tolerant netware but does for the rest.
-        */
-       if (*(unsigned short *) rawp == 0xFFFF)
-               return htons(ETH_P_802_3);
-
-       /*
-        * Real 802.2 LLC
-        */
-       return htons(ETH_P_802_2);
-}
-#endif /* FASTER_VERSION */
 
 /*
  * We remember when the MAC gets set, so we don't override it later with
@@ -390,7 +325,7 @@ packet_fails_filter(__be16 type, struct br2684_vcc *brvcc, struct sk_buff *skb)
 
 static void br2684_close_vcc(struct br2684_vcc *brvcc)
 {
-       DPRINTK("removing VCC %p from dev %p\n", brvcc, brvcc->device);
+       pr_debug("removing VCC %p from dev %p\n", brvcc, brvcc->device);
        write_lock_irq(&devs_lock);
        list_del(&brvcc->brvccs);
        write_unlock_irq(&devs_lock);
@@ -406,9 +341,8 @@ static void br2684_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
        struct br2684_vcc *brvcc = BR2684_VCC(atmvcc);
        struct net_device *net_dev = brvcc->device;
        struct br2684_dev *brdev = BRPRIV(net_dev);
-       int plen = sizeof(llc_oui_pid_pad) + ETH_HLEN;
 
-       DPRINTK("br2684_push\n");
+       pr_debug("br2684_push\n");
 
        if (unlikely(skb == NULL)) {
                /* skb==NULL means VCC is being destroyed */
@@ -425,46 +359,52 @@ static void br2684_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
 
        skb_debug(skb);
        atm_return(atmvcc, skb->truesize);
-       DPRINTK("skb from brdev %p\n", brdev);
+       pr_debug("skb from brdev %p\n", brdev);
        if (brvcc->encaps == e_llc) {
+
+               if (skb->len > 7 && skb->data[7] == 0x01)
+                       __skb_trim(skb, skb->len - 4);
+
+               /* accept packets that have "ipv[46]" in the snap header */
+               if ((skb->len >= (sizeof(llc_oui_ipv4)))
+                   && (memcmp(skb->data, llc_oui_ipv4, sizeof(llc_oui_ipv4) - BR2684_ETHERTYPE_LEN) == 0)) {
+                       if (memcmp(skb->data + 6, ethertype_ipv6, sizeof(ethertype_ipv6)) == 0)
+                               skb->protocol = __constant_htons(ETH_P_IPV6);
+                       else if (memcmp(skb->data + 6, ethertype_ipv4, sizeof(ethertype_ipv4)) == 0)
+                               skb->protocol = __constant_htons(ETH_P_IP);
+                       else {
+                               brdev->stats.rx_errors++;
+                               dev_kfree_skb(skb);
+                               return;
+                       }
+                       skb_pull(skb, sizeof(llc_oui_ipv4));
+                       skb_reset_network_header(skb);
+                       skb->pkt_type = PACKET_HOST;
+
                /* let us waste some time for checking the encapsulation.
                   Note, that only 7 char is checked so frames with a valid FCS
                   are also accepted (but FCS is not checked of course) */
-               if (memcmp(skb->data, llc_oui_pid_pad, 7)) {
+               } else if ((skb->len >= sizeof(llc_oui_pid_pad)) &&
+                          (memcmp(skb->data, llc_oui_pid_pad, 7) == 0)) {
+                       skb_pull(skb, sizeof(llc_oui_pid_pad));
+                       skb->protocol = eth_type_trans(skb, net_dev);
+               } else {
                        brdev->stats.rx_errors++;
                        dev_kfree_skb(skb);
                        return;
                }
 
-               /* Strip FCS if present */
-               if (skb->len > 7 && skb->data[7] == 0x01)
-                       __skb_trim(skb, skb->len - 4);
        } else {
-               plen = PADLEN + ETH_HLEN;       /* pad, dstmac,srcmac, ethtype */
                /* first 2 chars should be 0 */
                if (*((u16 *) (skb->data)) != 0) {
                        brdev->stats.rx_errors++;
                        dev_kfree_skb(skb);
                        return;
                }
-       }
-       if (skb->len < plen) {
-               brdev->stats.rx_errors++;
-               dev_kfree_skb(skb);     /* dev_ not needed? */
-               return;
+               skb_pull(skb, BR2684_PAD_LEN + ETH_HLEN); /* pad, dstmac, srcmac, ethtype */
+               skb->protocol = eth_type_trans(skb, net_dev);
        }
 
-#ifdef FASTER_VERSION
-       /* FIXME: tcpdump shows that pointer to mac header is 2 bytes earlier,
-          than should be. What else should I set? */
-       skb_pull(skb, plen);
-       skb_set_mac_header(skb, -ETH_HLEN);
-       skb->pkt_type = PACKET_HOST;
-       skb->protocol = br_type_trans(skb, net_dev);
-#else
-       skb_pull(skb, plen - ETH_HLEN);
-       skb->protocol = eth_type_trans(skb, net_dev);
-#endif /* FASTER_VERSION */
 #ifdef CONFIG_ATM_BR2684_IPFILTER
        if (unlikely(packet_fails_filter(skb->protocol, brvcc, skb))) {
                brdev->stats.rx_dropped++;
@@ -474,7 +414,7 @@ static void br2684_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
 #endif /* CONFIG_ATM_BR2684_IPFILTER */
        skb->dev = net_dev;
        ATM_SKB(skb)->vcc = atmvcc;     /* needed ? */
-       DPRINTK("received packet's protocol: %x\n", ntohs(skb->protocol));
+       pr_debug("received packet's protocol: %x\n", ntohs(skb->protocol));
        skb_debug(skb);
        if (unlikely(!(net_dev->flags & IFF_UP))) {
                /* sigh, interface is down */
@@ -532,7 +472,7 @@ Note: we do not have explicit unassign, but look at _push()
                err = -EINVAL;
                goto error;
        }
-       DPRINTK("br2684_regvcc vcc=%p, encaps=%d, brvcc=%p\n", atmvcc, be.encaps,
+       pr_debug("br2684_regvcc vcc=%p, encaps=%d, brvcc=%p\n", atmvcc, be.encaps,
                brvcc);
        if (list_empty(&brdev->brvccs) && !brdev->mac_was_set) {
                unsigned char *esi = atmvcc->dev->esi;
@@ -590,13 +530,6 @@ static void br2684_setup(struct net_device *netdev)
        ether_setup(netdev);
        brdev->net_dev = netdev;
 
-#ifdef FASTER_VERSION
-       my_eth_header = netdev->hard_header;
-       netdev->hard_header = br2684_header;
-       my_eth_header_cache = netdev->hard_header_cache;
-       netdev->hard_header_cache = br2684_header_cache;
-       netdev->hard_header_len = sizeof(llc_oui_pid_pad) + ETH_HLEN;   /* 10 + 14 */
-#endif
        my_eth_mac_addr = netdev->set_mac_address;
        netdev->set_mac_address = br2684_mac_addr;
        netdev->hard_start_xmit = br2684_start_xmit;
@@ -605,31 +538,58 @@ static void br2684_setup(struct net_device *netdev)
        INIT_LIST_HEAD(&brdev->brvccs);
 }
 
+static void br2684_setup_routed(struct net_device *netdev)
+{
+       struct br2684_dev *brdev = BRPRIV(netdev);
+       brdev->net_dev = netdev;
+
+       netdev->hard_header_len = 0;
+       my_eth_mac_addr = netdev->set_mac_address;
+       netdev->set_mac_address = br2684_mac_addr;
+       netdev->hard_start_xmit = br2684_start_xmit;
+       netdev->get_stats = br2684_get_stats;
+       netdev->addr_len = 0;
+       netdev->mtu = 1500;
+       netdev->type = ARPHRD_PPP;
+       netdev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+       netdev->tx_queue_len = 100;
+       INIT_LIST_HEAD(&brdev->brvccs);
+}
+
 static int br2684_create(void __user *arg)
 {
        int err;
        struct net_device *netdev;
        struct br2684_dev *brdev;
        struct atm_newif_br2684 ni;
+       enum br2684_payload payload;
 
-       DPRINTK("br2684_create\n");
+       pr_debug("br2684_create\n");
 
        if (copy_from_user(&ni, arg, sizeof ni)) {
                return -EFAULT;
        }
+
+       if (ni.media & BR2684_FLAG_ROUTED)
+               payload = p_routed;
+       else
+               payload = p_bridged;
+       ni.media &= 0xffff; /* strip flags */
+
        if (ni.media != BR2684_MEDIA_ETHERNET || ni.mtu != 1500) {
                return -EINVAL;
        }
 
        netdev = alloc_netdev(sizeof(struct br2684_dev),
                              ni.ifname[0] ? ni.ifname : "nas%d",
-                             br2684_setup);
+                             (payload == p_routed) ?
+                              br2684_setup_routed : br2684_setup);
        if (!netdev)
                return -ENOMEM;
 
        brdev = BRPRIV(netdev);
 
-       DPRINTK("registered netdev %s\n", netdev->name);
+       pr_debug("registered netdev %s\n", netdev->name);
        /* open, stop, do_ioctl ? */
        err = register_netdev(netdev);
        if (err < 0) {
@@ -639,6 +599,7 @@ static int br2684_create(void __user *arg)
        }
 
        write_lock_irq(&devs_lock);
+       brdev->payload = payload;
        brdev->number = list_empty(&br2684_devs) ? 1 :
            BRPRIV(list_entry_brdev(br2684_devs.prev))->number + 1;
        list_add_tail(&brdev->br2684_devs, &br2684_devs);
@@ -715,31 +676,23 @@ static int br2684_seq_show(struct seq_file *seq, void *v)
                        br2684_devs);
        const struct net_device *net_dev = brdev->net_dev;
        const struct br2684_vcc *brvcc;
+       DECLARE_MAC_BUF(mac);
 
-       seq_printf(seq, "dev %.16s: num=%d, mac=%02X:%02X:"
-                      "%02X:%02X:%02X:%02X (%s)\n", net_dev->name,
-                      brdev->number,
-                      net_dev->dev_addr[0],
-                      net_dev->dev_addr[1],
-                      net_dev->dev_addr[2],
-                      net_dev->dev_addr[3],
-                      net_dev->dev_addr[4],
-                      net_dev->dev_addr[5],
-                      brdev->mac_was_set ? "set" : "auto");
+       seq_printf(seq, "dev %.16s: num=%d, mac=%s (%s)\n",
+                  net_dev->name,
+                  brdev->number,
+                  print_mac(mac, net_dev->dev_addr),
+                  brdev->mac_was_set ? "set" : "auto");
 
        list_for_each_entry(brvcc, &brdev->brvccs, brvccs) {
-               seq_printf(seq, "  vcc %d.%d.%d: encaps=%s"
-#ifndef FASTER_VERSION
-                                   ", failed copies %u/%u"
-#endif /* FASTER_VERSION */
-                                   "\n", brvcc->atmvcc->dev->number,
-                                   brvcc->atmvcc->vpi, brvcc->atmvcc->vci,
-                                   (brvcc->encaps == e_llc) ? "LLC" : "VC"
-#ifndef FASTER_VERSION
-                                   , brvcc->copies_failed
-                                   , brvcc->copies_needed
-#endif /* FASTER_VERSION */
-                                   );
+               seq_printf(seq, "  vcc %d.%d.%d: encaps=%s payload=%s"
+                               ", failed copies %u/%u"
+                               "\n", brvcc->atmvcc->dev->number,
+                                     brvcc->atmvcc->vpi, brvcc->atmvcc->vci,
+                                     (brvcc->encaps == e_llc) ? "LLC" : "VC",
+                                     (brdev->payload == p_bridged) ? "bridged" : "routed",
+                                     brvcc->copies_failed,
+                                     brvcc->copies_needed);
 #ifdef CONFIG_ATM_BR2684_IPFILTER
 #define b1(var, byte)  ((u8 *) &brvcc->filter.var)[byte]
 #define bs(var)                b1(var, 0), b1(var, 1), b1(var, 2), b1(var, 3)