*     @klen: key length
  *     @dlen: data length
  *     @size: number of set elements
+ *     @field_len: length of each field in concatenation, bytes
+ *     @field_count: number of concatenated fields in element
  */
 struct nft_set_desc {
        unsigned int            klen;
        unsigned int            dlen;
        unsigned int            size;
+       u8                      field_len[NFT_REG32_COUNT];
+       u8                      field_count;
 };
 
 /**
  *     @dtype: data type (verdict or numeric type defined by userspace)
  *     @objtype: object type (see NFT_OBJECT_* definitions)
  *     @size: maximum set size
+ *     @field_len: length of each field in concatenation, bytes
+ *     @field_count: number of concatenated fields in element
  *     @use: number of rules references to this set
  *     @nelems: number of elements
  *     @ndeact: number of deactivated elements queued for removal
        u32                             dtype;
        u32                             objtype;
        u32                             size;
+       u8                              field_len[NFT_REG32_COUNT];
+       u8                              field_count;
        u32                             use;
        atomic_t                        nelems;
        u32                             ndeact;
 
 
 #define NFT_REG_SIZE   16
 #define NFT_REG32_SIZE 4
+#define NFT_REG32_COUNT        (NFT_REG32_15 - NFT_REG32_00 + 1)
 
 /**
  * enum nft_verdicts - nf_tables internal verdicts
  * enum nft_set_desc_attributes - set element description
  *
  * @NFTA_SET_DESC_SIZE: number of elements in set (NLA_U32)
+ * @NFTA_SET_DESC_CONCAT: description of field concatenation (NLA_NESTED)
  */
 enum nft_set_desc_attributes {
        NFTA_SET_DESC_UNSPEC,
        NFTA_SET_DESC_SIZE,
+       NFTA_SET_DESC_CONCAT,
        __NFTA_SET_DESC_MAX
 };
 #define NFTA_SET_DESC_MAX      (__NFTA_SET_DESC_MAX - 1)
 
+/**
+ * enum nft_set_field_attributes - attributes of concatenated fields
+ *
+ * @NFTA_SET_FIELD_LEN: length of single field, in bits (NLA_U32)
+ */
+enum nft_set_field_attributes {
+       NFTA_SET_FIELD_UNSPEC,
+       NFTA_SET_FIELD_LEN,
+       __NFTA_SET_FIELD_MAX
+};
+#define NFTA_SET_FIELD_MAX     (__NFTA_SET_FIELD_MAX - 1)
+
 /**
  * enum nft_set_attributes - nf_tables set netlink attributes
  *
 
 
 static const struct nla_policy nft_set_desc_policy[NFTA_SET_DESC_MAX + 1] = {
        [NFTA_SET_DESC_SIZE]            = { .type = NLA_U32 },
+       [NFTA_SET_DESC_CONCAT]          = { .type = NLA_NESTED },
 };
 
 static int nft_ctx_init_from_setattr(struct nft_ctx *ctx, struct net *net,
        return cpu_to_be64(jiffies64_to_msecs(input));
 }
 
+static int nf_tables_fill_set_concat(struct sk_buff *skb,
+                                    const struct nft_set *set)
+{
+       struct nlattr *concat, *field;
+       int i;
+
+       concat = nla_nest_start_noflag(skb, NFTA_SET_DESC_CONCAT);
+       if (!concat)
+               return -ENOMEM;
+
+       for (i = 0; i < set->field_count; i++) {
+               field = nla_nest_start_noflag(skb, NFTA_LIST_ELEM);
+               if (!field)
+                       return -ENOMEM;
+
+               if (nla_put_be32(skb, NFTA_SET_FIELD_LEN,
+                                htonl(set->field_len[i])))
+                       return -ENOMEM;
+
+               nla_nest_end(skb, field);
+       }
+
+       nla_nest_end(skb, concat);
+
+       return 0;
+}
+
 static int nf_tables_fill_set(struct sk_buff *skb, const struct nft_ctx *ctx,
                              const struct nft_set *set, u16 event, u16 flags)
 {
                goto nla_put_failure;
 
        desc = nla_nest_start_noflag(skb, NFTA_SET_DESC);
+
        if (desc == NULL)
                goto nla_put_failure;
        if (set->size &&
            nla_put_be32(skb, NFTA_SET_DESC_SIZE, htonl(set->size)))
                goto nla_put_failure;
+
+       if (set->field_count > 1 &&
+           nf_tables_fill_set_concat(skb, set))
+               goto nla_put_failure;
+
        nla_nest_end(skb, desc);
 
        nlmsg_end(skb, nlh);
        return err;
 }
 
+static const struct nla_policy nft_concat_policy[NFTA_SET_FIELD_MAX + 1] = {
+       [NFTA_SET_FIELD_LEN]    = { .type = NLA_U32 },
+};
+
+static int nft_set_desc_concat_parse(const struct nlattr *attr,
+                                    struct nft_set_desc *desc)
+{
+       struct nlattr *tb[NFTA_SET_FIELD_MAX + 1];
+       u32 len;
+       int err;
+
+       err = nla_parse_nested_deprecated(tb, NFTA_SET_FIELD_MAX, attr,
+                                         nft_concat_policy, NULL);
+       if (err < 0)
+               return err;
+
+       if (!tb[NFTA_SET_FIELD_LEN])
+               return -EINVAL;
+
+       len = ntohl(nla_get_be32(tb[NFTA_SET_FIELD_LEN]));
+
+       if (len * BITS_PER_BYTE / 32 > NFT_REG32_COUNT)
+               return -E2BIG;
+
+       desc->field_len[desc->field_count++] = len;
+
+       return 0;
+}
+
+static int nft_set_desc_concat(struct nft_set_desc *desc,
+                              const struct nlattr *nla)
+{
+       struct nlattr *attr;
+       int rem, err;
+
+       nla_for_each_nested(attr, nla, rem) {
+               if (nla_type(attr) != NFTA_LIST_ELEM)
+                       return -EINVAL;
+
+               err = nft_set_desc_concat_parse(attr, desc);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
 static int nf_tables_set_desc_parse(struct nft_set_desc *desc,
                                    const struct nlattr *nla)
 {
 
        if (da[NFTA_SET_DESC_SIZE] != NULL)
                desc->size = ntohl(nla_get_be32(da[NFTA_SET_DESC_SIZE]));
+       if (da[NFTA_SET_DESC_CONCAT])
+               err = nft_set_desc_concat(desc, da[NFTA_SET_DESC_CONCAT]);
 
-       return 0;
+       return err;
 }
 
 static int nf_tables_newset(struct net *net, struct sock *nlsk,
        unsigned char *udata;
        u16 udlen;
        int err;
+       int i;
 
        if (nla[NFTA_SET_TABLE] == NULL ||
            nla[NFTA_SET_NAME] == NULL ||
        set->gc_int = gc_int;
        set->handle = nf_tables_alloc_handle(table);
 
+       set->field_count = desc.field_count;
+       for (i = 0; i < desc.field_count; i++)
+               set->field_len[i] = desc.field_len[i];
+
        err = ops->init(set, &desc, nla);
        if (err < 0)
                goto err3;