]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - net/xfrm/xfrm_device.c
xfrm: Add an IPsec hardware offloading API
[karo-tx-linux.git] / net / xfrm / xfrm_device.c
index 34a260a61be939e77235990d864989454c802159..9bac2ba9052c564ba43cbf617ee25ba13e7981cb 100644 (file)
 #include <net/xfrm.h>
 #include <linux/notifier.h>
 
+int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
+                      struct xfrm_user_offload *xuo)
+{
+       int err;
+       struct dst_entry *dst;
+       struct net_device *dev;
+       struct xfrm_state_offload *xso = &x->xso;
+       xfrm_address_t *saddr;
+       xfrm_address_t *daddr;
+
+       if (!x->type_offload)
+               return 0;
+
+       /* We don't yet support UDP encapsulation, TFC padding and ESN. */
+       if (x->encap || x->tfcpad || (x->props.flags & XFRM_STATE_ESN))
+               return 0;
+
+       dev = dev_get_by_index(net, xuo->ifindex);
+       if (!dev) {
+               if (!(xuo->flags & XFRM_OFFLOAD_INBOUND)) {
+                       saddr = &x->props.saddr;
+                       daddr = &x->id.daddr;
+               } else {
+                       saddr = &x->id.daddr;
+                       daddr = &x->props.saddr;
+               }
+
+               dst = __xfrm_dst_lookup(net, 0, 0, saddr, daddr, x->props.family);
+               if (IS_ERR(dst))
+                       return 0;
+
+               dev = dst->dev;
+
+               dev_hold(dev);
+               dst_release(dst);
+       }
+
+       if (!dev->xfrmdev_ops || !dev->xfrmdev_ops->xdo_dev_state_add) {
+               dev_put(dev);
+               return 0;
+       }
+
+       xso->dev = dev;
+       xso->num_exthdrs = 1;
+       xso->flags = xuo->flags;
+
+       err = dev->xfrmdev_ops->xdo_dev_state_add(x);
+       if (err) {
+               dev_put(dev);
+               return err;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(xfrm_dev_state_add);
+
+bool xfrm_dev_offload_ok(struct sk_buff *skb, struct xfrm_state *x)
+{
+       int mtu;
+       struct dst_entry *dst = skb_dst(skb);
+       struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
+       struct net_device *dev = x->xso.dev;
+
+       if (!x->type_offload || x->encap)
+               return false;
+
+       if ((x->xso.offload_handle && (dev == dst->path->dev)) &&
+            !dst->child->xfrm && x->type->get_mtu) {
+               mtu = x->type->get_mtu(x, xdst->child_mtu_cached);
+
+               if (skb->len <= mtu)
+                       goto ok;
+
+               if (skb_is_gso(skb) && skb_gso_validate_mtu(skb, mtu))
+                       goto ok;
+       }
+
+       return false;
+
+ok:
+       if (dev && dev->xfrmdev_ops && dev->xfrmdev_ops->xdo_dev_offload_ok)
+               return x->xso.dev->xfrmdev_ops->xdo_dev_offload_ok(skb, x);
+
+       return true;
+}
+EXPORT_SYMBOL_GPL(xfrm_dev_offload_ok);
+
+int xfrm_dev_register(struct net_device *dev)
+{
+       if ((dev->features & NETIF_F_HW_ESP) && !dev->xfrmdev_ops)
+               return NOTIFY_BAD;
+       if ((dev->features & NETIF_F_HW_ESP_TX_CSUM) &&
+           !(dev->features & NETIF_F_HW_ESP))
+               return NOTIFY_BAD;
+
+       return NOTIFY_DONE;
+}
+
+static int xfrm_dev_unregister(struct net_device *dev)
+{
+       return NOTIFY_DONE;
+}
+
+static int xfrm_dev_feat_change(struct net_device *dev)
+{
+       if ((dev->features & NETIF_F_HW_ESP) && !dev->xfrmdev_ops)
+               return NOTIFY_BAD;
+       else if (!(dev->features & NETIF_F_HW_ESP))
+               dev->xfrmdev_ops = NULL;
+
+       if ((dev->features & NETIF_F_HW_ESP_TX_CSUM) &&
+           !(dev->features & NETIF_F_HW_ESP))
+               return NOTIFY_BAD;
+
+       return NOTIFY_DONE;
+}
+
+static int xfrm_dev_down(struct net_device *dev)
+{
+       if (dev->hw_features & NETIF_F_HW_ESP)
+               xfrm_dev_state_flush(dev_net(dev), dev, true);
+
+       xfrm_garbage_collect(dev_net(dev));
+
+       return NOTIFY_DONE;
+}
+
 static int xfrm_dev_event(struct notifier_block *this, unsigned long event, void *ptr)
 {
        struct net_device *dev = netdev_notifier_info_to_dev(ptr);
 
        switch (event) {
+       case NETDEV_REGISTER:
+               return xfrm_dev_register(dev);
+
+       case NETDEV_UNREGISTER:
+               return xfrm_dev_unregister(dev);
+
+       case NETDEV_FEAT_CHANGE:
+               return xfrm_dev_feat_change(dev);
+
        case NETDEV_DOWN:
-               xfrm_garbage_collect(dev_net(dev));
+               return xfrm_dev_down(dev);
        }
        return NOTIFY_DONE;
 }