#include <net/addrconf.h>
 #include <net/xfrm.h>
 #include <net/net_namespace.h>
+#include <net/dst_metadata.h>
 #include <net/netns/generic.h>
 #include <linux/etherdevice.h>
 
 struct xfrmi_net {
        /* lists for storing interfaces in use */
        struct xfrm_if __rcu *xfrmi[XFRMI_HASH_SIZE];
+       struct xfrm_if __rcu *collect_md_xfrmi;
 };
 
 #define for_each_xfrmi_rcu(start, xi) \
                        return xi;
        }
 
+       xi = rcu_dereference(xfrmn->collect_md_xfrmi);
+       if (xi && (xi->dev->flags & IFF_UP))
+               return xi;
+
        return NULL;
 }
 
-static struct xfrm_if *xfrmi_decode_session(struct sk_buff *skb,
-                                           unsigned short family)
+static bool xfrmi_decode_session(struct sk_buff *skb,
+                                unsigned short family,
+                                struct xfrm_if_decode_session_result *res)
 {
        struct net_device *dev;
+       struct xfrm_if *xi;
        int ifindex = 0;
 
        if (!secpath_exists(skb) || !skb->dev)
-               return NULL;
+               return false;
 
        switch (family) {
        case AF_INET6:
        }
 
        if (!dev || !(dev->flags & IFF_UP))
-               return NULL;
+               return false;
        if (dev->netdev_ops != &xfrmi_netdev_ops)
-               return NULL;
+               return false;
 
-       return netdev_priv(dev);
+       xi = netdev_priv(dev);
+       res->net = xi->net;
+
+       if (xi->p.collect_md)
+               res->if_id = xfrm_input_state(skb)->if_id;
+       else
+               res->if_id = xi->p.if_id;
+       return true;
 }
 
 static void xfrmi_link(struct xfrmi_net *xfrmn, struct xfrm_if *xi)
        if (err < 0)
                goto out;
 
-       xfrmi_link(xfrmn, xi);
+       if (xi->p.collect_md)
+               rcu_assign_pointer(xfrmn->collect_md_xfrmi, xi);
+       else
+               xfrmi_link(xfrmn, xi);
 
        return 0;
 
        struct xfrm_if *xi = netdev_priv(dev);
        struct xfrmi_net *xfrmn = net_generic(xi->net, xfrmi_net_id);
 
-       xfrmi_unlink(xfrmn, xi);
+       if (xi->p.collect_md)
+               RCU_INIT_POINTER(xfrmn->collect_md_xfrmi, NULL);
+       else
+               xfrmi_unlink(xfrmn, xi);
 }
 
 static void xfrmi_scrub_packet(struct sk_buff *skb, bool xnet)
        struct xfrm_state *x;
        struct xfrm_if *xi;
        bool xnet;
+       int link;
 
        if (err && !secpath_exists(skb))
                return 0;
        if (!xi)
                return 1;
 
+       link = skb->dev->ifindex;
        dev = xi->dev;
        skb->dev = dev;
 
        }
 
        xfrmi_scrub_packet(skb, xnet);
+       if (xi->p.collect_md) {
+               struct metadata_dst *md_dst;
+
+               md_dst = metadata_dst_alloc(0, METADATA_XFRM, GFP_ATOMIC);
+               if (!md_dst)
+                       return -ENOMEM;
+
+               md_dst->u.xfrm_info.if_id = x->if_id;
+               md_dst->u.xfrm_info.link = link;
+               skb_dst_set(skb, (struct dst_entry *)md_dst);
+       }
        dev_sw_netstats_rx_add(dev, skb->len);
 
        return 0;
        struct net_device *tdev;
        struct xfrm_state *x;
        int err = -1;
+       u32 if_id;
        int mtu;
 
+       if (xi->p.collect_md) {
+               struct xfrm_md_info *md_info = skb_xfrm_md_info(skb);
+
+               if (unlikely(!md_info))
+                       return -EINVAL;
+
+               if_id = md_info->if_id;
+               fl->flowi_oif = md_info->link;
+       } else {
+               if_id = xi->p.if_id;
+       }
+
        dst_hold(dst);
-       dst = xfrm_lookup_with_ifid(xi->net, dst, fl, NULL, 0, xi->p.if_id);
+       dst = xfrm_lookup_with_ifid(xi->net, dst, fl, NULL, 0, if_id);
        if (IS_ERR(dst)) {
                err = PTR_ERR(dst);
                dst = NULL;
        if (!x)
                goto tx_err_link_failure;
 
-       if (x->if_id != xi->p.if_id)
+       if (x->if_id != if_id)
                goto tx_err_link_failure;
 
        tdev = dst->dev;
 
        if (data[IFLA_XFRM_IF_ID])
                parms->if_id = nla_get_u32(data[IFLA_XFRM_IF_ID]);
+
+       if (data[IFLA_XFRM_COLLECT_METADATA])
+               parms->collect_md = true;
 }
 
 static int xfrmi_newlink(struct net *src_net, struct net_device *dev,
        int err;
 
        xfrmi_netlink_parms(data, &p);
-       if (!p.if_id) {
-               NL_SET_ERR_MSG(extack, "if_id must be non zero");
-               return -EINVAL;
-       }
+       if (p.collect_md) {
+               struct xfrmi_net *xfrmn = net_generic(net, xfrmi_net_id);
 
-       xi = xfrmi_locate(net, &p);
-       if (xi)
-               return -EEXIST;
+               if (p.link || p.if_id) {
+                       NL_SET_ERR_MSG(extack, "link and if_id must be zero");
+                       return -EINVAL;
+               }
+
+               if (rtnl_dereference(xfrmn->collect_md_xfrmi))
+                       return -EEXIST;
+
+       } else {
+               if (!p.if_id) {
+                       NL_SET_ERR_MSG(extack, "if_id must be non zero");
+                       return -EINVAL;
+               }
+
+               xi = xfrmi_locate(net, &p);
+               if (xi)
+                       return -EEXIST;
+       }
 
        xi = netdev_priv(dev);
        xi->p = p;
                return -EINVAL;
        }
 
+       if (p.collect_md) {
+               NL_SET_ERR_MSG(extack, "collect_md can't be changed");
+               return -EINVAL;
+       }
+
        xi = xfrmi_locate(net, &p);
        if (!xi) {
                xi = netdev_priv(dev);
        } else {
                if (xi->dev != dev)
                        return -EEXIST;
+               if (xi->p.collect_md) {
+                       NL_SET_ERR_MSG(extack,
+                                      "device can't be changed to collect_md");
+                       return -EINVAL;
+               }
        }
 
        return xfrmi_update(xi, &p);
                nla_total_size(4) +
                /* IFLA_XFRM_IF_ID */
                nla_total_size(4) +
+               /* IFLA_XFRM_COLLECT_METADATA */
+               nla_total_size(0) +
                0;
 }
 
        struct xfrm_if_parms *parm = &xi->p;
 
        if (nla_put_u32(skb, IFLA_XFRM_LINK, parm->link) ||
-           nla_put_u32(skb, IFLA_XFRM_IF_ID, parm->if_id))
+           nla_put_u32(skb, IFLA_XFRM_IF_ID, parm->if_id) ||
+           (xi->p.collect_md && nla_put_flag(skb, IFLA_XFRM_COLLECT_METADATA)))
                goto nla_put_failure;
        return 0;
 
 }
 
 static const struct nla_policy xfrmi_policy[IFLA_XFRM_MAX + 1] = {
-       [IFLA_XFRM_LINK]        = { .type = NLA_U32 },
-       [IFLA_XFRM_IF_ID]       = { .type = NLA_U32 },
+       [IFLA_XFRM_UNSPEC]              = { .strict_start_type = IFLA_XFRM_COLLECT_METADATA },
+       [IFLA_XFRM_LINK]                = { .type = NLA_U32 },
+       [IFLA_XFRM_IF_ID]               = { .type = NLA_U32 },
+       [IFLA_XFRM_COLLECT_METADATA]    = { .type = NLA_FLAG },
 };
 
 static struct rtnl_link_ops xfrmi_link_ops __read_mostly = {
                             xip = &xi->next)
                                unregister_netdevice_queue(xi->dev, &list);
                }
+               xi = rtnl_dereference(xfrmn->collect_md_xfrmi);
+               if (xi)
+                       unregister_netdevice_queue(xi->dev, &list);
        }
        unregister_netdevice_many(&list);
        rtnl_unlock();