Commit 8af750d7 authored by David S. Miller's avatar David S. Miller

Merge git://git.kernel.org/pub/scm/linux/kernel/git/pablo/nftables

Pablo Neira Ayuso says:

====================
Netfilter/nftables updates for net-next

The following patchset contains Netfilter/nftables updates for net-next,
most relevantly they are:

1) Add set element update notification via netlink, from Arturo Borrero.

2) Put all object updates in one single message batch that is sent to
   kernel-space. Before this patch only rules where included in the batch.
   This series also introduces the generic transaction infrastructure so
   updates to all objects (tables, chains, rules and sets) are applied in
   an all-or-nothing fashion, these series from me.

3) Defer release of objects via call_rcu to reduce the time required to
   commit changes. The assumption is that all objects are destroyed in
   reverse order to ensure that dependencies betweem them are fulfilled
   (ie. rules and sets are destroyed first, then chains, and finally
   tables).

4) Allow to match by bridge port name, from Tomasz Bursztyka. This series
   include two patches to prepare this new feature.

5) Implement the proper set selection based on the characteristics of the
   data. The new infrastructure also allows you to specify your preferences
   in terms of memory and computational complexity so the underlying set
   type is also selected according to your needs, from Patrick McHardy.

6) Several cleanup patches for nft expressions, including one minor possible
   compilation breakage due to missing mark support, also from Patrick.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 758bd61a c7c32e72
...@@ -72,21 +72,23 @@ static inline void nft_data_debug(const struct nft_data *data) ...@@ -72,21 +72,23 @@ static inline void nft_data_debug(const struct nft_data *data)
* struct nft_ctx - nf_tables rule/set context * struct nft_ctx - nf_tables rule/set context
* *
* @net: net namespace * @net: net namespace
* @skb: netlink skb
* @nlh: netlink message header
* @afi: address family info * @afi: address family info
* @table: the table the chain is contained in * @table: the table the chain is contained in
* @chain: the chain the rule is contained in * @chain: the chain the rule is contained in
* @nla: netlink attributes * @nla: netlink attributes
* @portid: netlink portID of the original message
* @seq: netlink sequence number
* @report: notify via unicast netlink message
*/ */
struct nft_ctx { struct nft_ctx {
struct net *net; struct net *net;
const struct sk_buff *skb; struct nft_af_info *afi;
const struct nlmsghdr *nlh; struct nft_table *table;
const struct nft_af_info *afi; struct nft_chain *chain;
const struct nft_table *table;
const struct nft_chain *chain;
const struct nlattr * const *nla; const struct nlattr * const *nla;
u32 portid;
u32 seq;
bool report;
}; };
struct nft_data_desc { struct nft_data_desc {
...@@ -145,6 +147,44 @@ struct nft_set_iter { ...@@ -145,6 +147,44 @@ struct nft_set_iter {
const struct nft_set_elem *elem); const struct nft_set_elem *elem);
}; };
/**
* struct nft_set_desc - description of set elements
*
* @klen: key length
* @dlen: data length
* @size: number of set elements
*/
struct nft_set_desc {
unsigned int klen;
unsigned int dlen;
unsigned int size;
};
/**
* enum nft_set_class - performance class
*
* @NFT_LOOKUP_O_1: constant, O(1)
* @NFT_LOOKUP_O_LOG_N: logarithmic, O(log N)
* @NFT_LOOKUP_O_N: linear, O(N)
*/
enum nft_set_class {
NFT_SET_CLASS_O_1,
NFT_SET_CLASS_O_LOG_N,
NFT_SET_CLASS_O_N,
};
/**
* struct nft_set_estimate - estimation of memory and performance
* characteristics
*
* @size: required memory
* @class: lookup performance class
*/
struct nft_set_estimate {
unsigned int size;
enum nft_set_class class;
};
/** /**
* struct nft_set_ops - nf_tables set operations * struct nft_set_ops - nf_tables set operations
* *
...@@ -174,7 +214,11 @@ struct nft_set_ops { ...@@ -174,7 +214,11 @@ struct nft_set_ops {
struct nft_set_iter *iter); struct nft_set_iter *iter);
unsigned int (*privsize)(const struct nlattr * const nla[]); unsigned int (*privsize)(const struct nlattr * const nla[]);
bool (*estimate)(const struct nft_set_desc *desc,
u32 features,
struct nft_set_estimate *est);
int (*init)(const struct nft_set *set, int (*init)(const struct nft_set *set,
const struct nft_set_desc *desc,
const struct nlattr * const nla[]); const struct nlattr * const nla[]);
void (*destroy)(const struct nft_set *set); void (*destroy)(const struct nft_set *set);
...@@ -194,6 +238,8 @@ void nft_unregister_set(struct nft_set_ops *ops); ...@@ -194,6 +238,8 @@ void nft_unregister_set(struct nft_set_ops *ops);
* @name: name of the set * @name: name of the set
* @ktype: key type (numeric type defined by userspace, not used in the kernel) * @ktype: key type (numeric type defined by userspace, not used in the kernel)
* @dtype: data type (verdict or numeric type defined by userspace) * @dtype: data type (verdict or numeric type defined by userspace)
* @size: maximum set size
* @nelems: number of elements
* @ops: set ops * @ops: set ops
* @flags: set flags * @flags: set flags
* @klen: key length * @klen: key length
...@@ -206,6 +252,8 @@ struct nft_set { ...@@ -206,6 +252,8 @@ struct nft_set {
char name[IFNAMSIZ]; char name[IFNAMSIZ];
u32 ktype; u32 ktype;
u32 dtype; u32 dtype;
u32 size;
u32 nelems;
/* runtime data below here */ /* runtime data below here */
const struct nft_set_ops *ops ____cacheline_aligned; const struct nft_set_ops *ops ____cacheline_aligned;
u16 flags; u16 flags;
...@@ -222,6 +270,8 @@ static inline void *nft_set_priv(const struct nft_set *set) ...@@ -222,6 +270,8 @@ static inline void *nft_set_priv(const struct nft_set *set)
struct nft_set *nf_tables_set_lookup(const struct nft_table *table, struct nft_set *nf_tables_set_lookup(const struct nft_table *table,
const struct nlattr *nla); const struct nlattr *nla);
struct nft_set *nf_tables_set_lookup_byid(const struct net *net,
const struct nlattr *nla);
/** /**
* struct nft_set_binding - nf_tables set binding * struct nft_set_binding - nf_tables set binding
...@@ -341,18 +391,75 @@ struct nft_rule { ...@@ -341,18 +391,75 @@ struct nft_rule {
}; };
/** /**
* struct nft_rule_trans - nf_tables rule update in transaction * struct nft_trans - nf_tables object update in transaction
* *
* @rcu_head: rcu head to defer release of transaction data
* @list: used internally * @list: used internally
* @ctx: rule context * @msg_type: message type
* @rule: rule that needs to be updated * @ctx: transaction context
* @data: internal information related to the transaction
*/ */
struct nft_rule_trans { struct nft_trans {
struct rcu_head rcu_head;
struct list_head list; struct list_head list;
int msg_type;
struct nft_ctx ctx; struct nft_ctx ctx;
char data[0];
};
struct nft_trans_rule {
struct nft_rule *rule; struct nft_rule *rule;
}; };
#define nft_trans_rule(trans) \
(((struct nft_trans_rule *)trans->data)->rule)
struct nft_trans_set {
struct nft_set *set;
u32 set_id;
};
#define nft_trans_set(trans) \
(((struct nft_trans_set *)trans->data)->set)
#define nft_trans_set_id(trans) \
(((struct nft_trans_set *)trans->data)->set_id)
struct nft_trans_chain {
bool update;
char name[NFT_CHAIN_MAXNAMELEN];
struct nft_stats __percpu *stats;
u8 policy;
};
#define nft_trans_chain_update(trans) \
(((struct nft_trans_chain *)trans->data)->update)
#define nft_trans_chain_name(trans) \
(((struct nft_trans_chain *)trans->data)->name)
#define nft_trans_chain_stats(trans) \
(((struct nft_trans_chain *)trans->data)->stats)
#define nft_trans_chain_policy(trans) \
(((struct nft_trans_chain *)trans->data)->policy)
struct nft_trans_table {
bool update;
bool enable;
};
#define nft_trans_table_update(trans) \
(((struct nft_trans_table *)trans->data)->update)
#define nft_trans_table_enable(trans) \
(((struct nft_trans_table *)trans->data)->enable)
struct nft_trans_elem {
struct nft_set *set;
struct nft_set_elem elem;
};
#define nft_trans_elem_set(trans) \
(((struct nft_trans_elem *)trans->data)->set)
#define nft_trans_elem(trans) \
(((struct nft_trans_elem *)trans->data)->elem)
static inline struct nft_expr *nft_expr_first(const struct nft_rule *rule) static inline struct nft_expr *nft_expr_first(const struct nft_rule *rule)
{ {
return (struct nft_expr *)&rule->data[0]; return (struct nft_expr *)&rule->data[0];
...@@ -385,6 +492,7 @@ static inline void *nft_userdata(const struct nft_rule *rule) ...@@ -385,6 +492,7 @@ static inline void *nft_userdata(const struct nft_rule *rule)
enum nft_chain_flags { enum nft_chain_flags {
NFT_BASE_CHAIN = 0x1, NFT_BASE_CHAIN = 0x1,
NFT_CHAIN_INACTIVE = 0x2,
}; };
/** /**
......
#ifndef _NFT_META_H_
#define _NFT_META_H_
struct nft_meta {
enum nft_meta_keys key:8;
union {
enum nft_registers dreg:8;
enum nft_registers sreg:8;
};
};
extern const struct nla_policy nft_meta_policy[];
int nft_meta_get_init(const struct nft_ctx *ctx,
const struct nft_expr *expr,
const struct nlattr * const tb[]);
int nft_meta_set_init(const struct nft_ctx *ctx,
const struct nft_expr *expr,
const struct nlattr * const tb[]);
int nft_meta_get_dump(struct sk_buff *skb,
const struct nft_expr *expr);
int nft_meta_set_dump(struct sk_buff *skb,
const struct nft_expr *expr);
void nft_meta_get_eval(const struct nft_expr *expr,
struct nft_data data[NFT_REG_MAX + 1],
const struct nft_pktinfo *pkt);
void nft_meta_set_eval(const struct nft_expr *expr,
struct nft_data data[NFT_REG_MAX + 1],
const struct nft_pktinfo *pkt);
#endif
...@@ -211,6 +211,29 @@ enum nft_set_flags { ...@@ -211,6 +211,29 @@ enum nft_set_flags {
NFT_SET_MAP = 0x8, NFT_SET_MAP = 0x8,
}; };
/**
* enum nft_set_policies - set selection policy
*
* @NFT_SET_POL_PERFORMANCE: prefer high performance over low memory use
* @NFT_SET_POL_MEMORY: prefer low memory use over high performance
*/
enum nft_set_policies {
NFT_SET_POL_PERFORMANCE,
NFT_SET_POL_MEMORY,
};
/**
* enum nft_set_desc_attributes - set element description
*
* @NFTA_SET_DESC_SIZE: number of elements in set (NLA_U32)
*/
enum nft_set_desc_attributes {
NFTA_SET_DESC_UNSPEC,
NFTA_SET_DESC_SIZE,
__NFTA_SET_DESC_MAX
};
#define NFTA_SET_DESC_MAX (__NFTA_SET_DESC_MAX - 1)
/** /**
* enum nft_set_attributes - nf_tables set netlink attributes * enum nft_set_attributes - nf_tables set netlink attributes
* *
...@@ -221,6 +244,9 @@ enum nft_set_flags { ...@@ -221,6 +244,9 @@ enum nft_set_flags {
* @NFTA_SET_KEY_LEN: key data length (NLA_U32) * @NFTA_SET_KEY_LEN: key data length (NLA_U32)
* @NFTA_SET_DATA_TYPE: mapping data type (NLA_U32) * @NFTA_SET_DATA_TYPE: mapping data type (NLA_U32)
* @NFTA_SET_DATA_LEN: mapping data length (NLA_U32) * @NFTA_SET_DATA_LEN: mapping data length (NLA_U32)
* @NFTA_SET_POLICY: selection policy (NLA_U32)
* @NFTA_SET_DESC: set description (NLA_NESTED)
* @NFTA_SET_ID: uniquely identifies a set in a transaction (NLA_U32)
*/ */
enum nft_set_attributes { enum nft_set_attributes {
NFTA_SET_UNSPEC, NFTA_SET_UNSPEC,
...@@ -231,6 +257,9 @@ enum nft_set_attributes { ...@@ -231,6 +257,9 @@ enum nft_set_attributes {
NFTA_SET_KEY_LEN, NFTA_SET_KEY_LEN,
NFTA_SET_DATA_TYPE, NFTA_SET_DATA_TYPE,
NFTA_SET_DATA_LEN, NFTA_SET_DATA_LEN,
NFTA_SET_POLICY,
NFTA_SET_DESC,
NFTA_SET_ID,
__NFTA_SET_MAX __NFTA_SET_MAX
}; };
#define NFTA_SET_MAX (__NFTA_SET_MAX - 1) #define NFTA_SET_MAX (__NFTA_SET_MAX - 1)
...@@ -266,12 +295,14 @@ enum nft_set_elem_attributes { ...@@ -266,12 +295,14 @@ enum nft_set_elem_attributes {
* @NFTA_SET_ELEM_LIST_TABLE: table of the set to be changed (NLA_STRING) * @NFTA_SET_ELEM_LIST_TABLE: table of the set to be changed (NLA_STRING)
* @NFTA_SET_ELEM_LIST_SET: name of the set to be changed (NLA_STRING) * @NFTA_SET_ELEM_LIST_SET: name of the set to be changed (NLA_STRING)
* @NFTA_SET_ELEM_LIST_ELEMENTS: list of set elements (NLA_NESTED: nft_set_elem_attributes) * @NFTA_SET_ELEM_LIST_ELEMENTS: list of set elements (NLA_NESTED: nft_set_elem_attributes)
* @NFTA_SET_ELEM_LIST_SET_ID: uniquely identifies a set in a transaction (NLA_U32)
*/ */
enum nft_set_elem_list_attributes { enum nft_set_elem_list_attributes {
NFTA_SET_ELEM_LIST_UNSPEC, NFTA_SET_ELEM_LIST_UNSPEC,
NFTA_SET_ELEM_LIST_TABLE, NFTA_SET_ELEM_LIST_TABLE,
NFTA_SET_ELEM_LIST_SET, NFTA_SET_ELEM_LIST_SET,
NFTA_SET_ELEM_LIST_ELEMENTS, NFTA_SET_ELEM_LIST_ELEMENTS,
NFTA_SET_ELEM_LIST_SET_ID,
__NFTA_SET_ELEM_LIST_MAX __NFTA_SET_ELEM_LIST_MAX
}; };
#define NFTA_SET_ELEM_LIST_MAX (__NFTA_SET_ELEM_LIST_MAX - 1) #define NFTA_SET_ELEM_LIST_MAX (__NFTA_SET_ELEM_LIST_MAX - 1)
...@@ -457,12 +488,14 @@ enum nft_cmp_attributes { ...@@ -457,12 +488,14 @@ enum nft_cmp_attributes {
* @NFTA_LOOKUP_SET: name of the set where to look for (NLA_STRING) * @NFTA_LOOKUP_SET: name of the set where to look for (NLA_STRING)
* @NFTA_LOOKUP_SREG: source register of the data to look for (NLA_U32: nft_registers) * @NFTA_LOOKUP_SREG: source register of the data to look for (NLA_U32: nft_registers)
* @NFTA_LOOKUP_DREG: destination register (NLA_U32: nft_registers) * @NFTA_LOOKUP_DREG: destination register (NLA_U32: nft_registers)
* @NFTA_LOOKUP_SET_ID: uniquely identifies a set in a transaction (NLA_U32)
*/ */
enum nft_lookup_attributes { enum nft_lookup_attributes {
NFTA_LOOKUP_UNSPEC, NFTA_LOOKUP_UNSPEC,
NFTA_LOOKUP_SET, NFTA_LOOKUP_SET,
NFTA_LOOKUP_SREG, NFTA_LOOKUP_SREG,
NFTA_LOOKUP_DREG, NFTA_LOOKUP_DREG,
NFTA_LOOKUP_SET_ID,
__NFTA_LOOKUP_MAX __NFTA_LOOKUP_MAX
}; };
#define NFTA_LOOKUP_MAX (__NFTA_LOOKUP_MAX - 1) #define NFTA_LOOKUP_MAX (__NFTA_LOOKUP_MAX - 1)
...@@ -536,6 +569,8 @@ enum nft_exthdr_attributes { ...@@ -536,6 +569,8 @@ enum nft_exthdr_attributes {
* @NFT_META_SECMARK: packet secmark (skb->secmark) * @NFT_META_SECMARK: packet secmark (skb->secmark)
* @NFT_META_NFPROTO: netfilter protocol * @NFT_META_NFPROTO: netfilter protocol
* @NFT_META_L4PROTO: layer 4 protocol number * @NFT_META_L4PROTO: layer 4 protocol number
* @NFT_META_BRI_IIFNAME: packet input bridge interface name
* @NFT_META_BRI_OIFNAME: packet output bridge interface name
*/ */
enum nft_meta_keys { enum nft_meta_keys {
NFT_META_LEN, NFT_META_LEN,
...@@ -555,6 +590,8 @@ enum nft_meta_keys { ...@@ -555,6 +590,8 @@ enum nft_meta_keys {
NFT_META_SECMARK, NFT_META_SECMARK,
NFT_META_NFPROTO, NFT_META_NFPROTO,
NFT_META_L4PROTO, NFT_META_L4PROTO,
NFT_META_BRI_IIFNAME,
NFT_META_BRI_OIFNAME,
}; };
/** /**
......
...@@ -16,4 +16,4 @@ bridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o br_mdb.o ...@@ -16,4 +16,4 @@ bridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o br_mdb.o
bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o
obj-$(CONFIG_BRIDGE_NF_EBTABLES) += netfilter/ obj-$(CONFIG_BRIDGE_NETFILTER) += netfilter/
...@@ -2,13 +2,25 @@ ...@@ -2,13 +2,25 @@
# Bridge netfilter configuration # Bridge netfilter configuration
# #
# #
config NF_TABLES_BRIDGE menuconfig NF_TABLES_BRIDGE
depends on NF_TABLES depends on NF_TABLES
select BRIDGE_NETFILTER
tristate "Ethernet Bridge nf_tables support" tristate "Ethernet Bridge nf_tables support"
if NF_TABLES_BRIDGE
config NFT_BRIDGE_META
tristate "Netfilter nf_table bridge meta support"
depends on NFT_META
help
Add support for bridge dedicated meta key.
endif # NF_TABLES_BRIDGE
menuconfig BRIDGE_NF_EBTABLES menuconfig BRIDGE_NF_EBTABLES
tristate "Ethernet Bridge tables (ebtables) support" tristate "Ethernet Bridge tables (ebtables) support"
depends on BRIDGE && NETFILTER depends on BRIDGE && NETFILTER
select BRIDGE_NETFILTER
select NETFILTER_XTABLES select NETFILTER_XTABLES
help help
ebtables is a general, extensible frame/packet identification ebtables is a general, extensible frame/packet identification
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# #
obj-$(CONFIG_NF_TABLES_BRIDGE) += nf_tables_bridge.o obj-$(CONFIG_NF_TABLES_BRIDGE) += nf_tables_bridge.o
obj-$(CONFIG_NFT_BRIDGE_META) += nft_meta_bridge.o
obj-$(CONFIG_BRIDGE_NF_EBTABLES) += ebtables.o obj-$(CONFIG_BRIDGE_NF_EBTABLES) += ebtables.o
......
/*
* Copyright (c) 2014 Intel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <net/netfilter/nf_tables.h>
#include <net/netfilter/nft_meta.h>
#include "../br_private.h"
static void nft_meta_bridge_get_eval(const struct nft_expr *expr,
struct nft_data data[NFT_REG_MAX + 1],
const struct nft_pktinfo *pkt)
{
const struct nft_meta *priv = nft_expr_priv(expr);
const struct net_device *in = pkt->in, *out = pkt->out;
struct nft_data *dest = &data[priv->dreg];
const struct net_bridge_port *p;
switch (priv->key) {
case NFT_META_BRI_IIFNAME:
if (in == NULL || (p = br_port_get_rcu(in)) == NULL)
goto err;
break;
case NFT_META_BRI_OIFNAME:
if (out == NULL || (p = br_port_get_rcu(out)) == NULL)
goto err;
break;
default:
goto out;
}
strncpy((char *)dest->data, p->br->dev->name, sizeof(dest->data));
return;
out:
return nft_meta_get_eval(expr, data, pkt);
err:
data[NFT_REG_VERDICT].verdict = NFT_BREAK;
}
static int nft_meta_bridge_get_init(const struct nft_ctx *ctx,
const struct nft_expr *expr,
const struct nlattr * const tb[])
{
struct nft_meta *priv = nft_expr_priv(expr);
int err;
priv->key = ntohl(nla_get_be32(tb[NFTA_META_KEY]));
switch (priv->key) {
case NFT_META_BRI_IIFNAME:
case NFT_META_BRI_OIFNAME:
break;
default:
return nft_meta_get_init(ctx, expr, tb);
}
priv->dreg = ntohl(nla_get_be32(tb[NFTA_META_DREG]));
err = nft_validate_output_register(priv->dreg);
if (err < 0)
return err;
err = nft_validate_data_load(ctx, priv->dreg, NULL, NFT_DATA_VALUE);
if (err < 0)
return err;
return 0;
}
static struct nft_expr_type nft_meta_bridge_type;
static const struct nft_expr_ops nft_meta_bridge_get_ops = {
.type = &nft_meta_bridge_type,
.size = NFT_EXPR_SIZE(sizeof(struct nft_meta)),
.eval = nft_meta_bridge_get_eval,
.init = nft_meta_bridge_get_init,
.dump = nft_meta_get_dump,
};
static const struct nft_expr_ops nft_meta_bridge_set_ops = {
.type = &nft_meta_bridge_type,
.size = NFT_EXPR_SIZE(sizeof(struct nft_meta)),
.eval = nft_meta_set_eval,
.init = nft_meta_set_init,
.dump = nft_meta_set_dump,
};
static const struct nft_expr_ops *
nft_meta_bridge_select_ops(const struct nft_ctx *ctx,
const struct nlattr * const tb[])
{
if (tb[NFTA_META_KEY] == NULL)
return ERR_PTR(-EINVAL);
if (tb[NFTA_META_DREG] && tb[NFTA_META_SREG])
return ERR_PTR(-EINVAL);
if (tb[NFTA_META_DREG])
return &nft_meta_bridge_get_ops;
if (tb[NFTA_META_SREG])
return &nft_meta_bridge_set_ops;
return ERR_PTR(-EINVAL);
}
static struct nft_expr_type nft_meta_bridge_type __read_mostly = {
.family = NFPROTO_BRIDGE,
.name = "meta",
.select_ops = &nft_meta_bridge_select_ops,
.policy = nft_meta_policy,
.maxattr = NFTA_META_MAX,
.owner = THIS_MODULE,
};
static int __init nft_meta_bridge_module_init(void)
{
return nft_register_expr(&nft_meta_bridge_type);
}
static void __exit nft_meta_bridge_module_exit(void)
{
nft_unregister_expr(&nft_meta_bridge_type);
}
module_init(nft_meta_bridge_module_init);
module_exit(nft_meta_bridge_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>");
MODULE_ALIAS_NFT_AF_EXPR(AF_BRIDGE, "meta");
This diff is collapsed.
...@@ -215,22 +215,14 @@ static void nft_ct_l3proto_module_put(uint8_t family) ...@@ -215,22 +215,14 @@ static void nft_ct_l3proto_module_put(uint8_t family)
nf_ct_l3proto_module_put(family); nf_ct_l3proto_module_put(family);
} }
static int nft_ct_init_validate_get(const struct nft_expr *expr, static int nft_ct_get_init(const struct nft_ctx *ctx,
const struct nft_expr *expr,
const struct nlattr * const tb[]) const struct nlattr * const tb[])
{ {
struct nft_ct *priv = nft_expr_priv(expr); struct nft_ct *priv = nft_expr_priv(expr);
int err;
if (tb[NFTA_CT_DIRECTION] != NULL) { priv->key = ntohl(nla_get_be32(tb[NFTA_CT_KEY]));
priv->dir = nla_get_u8(tb[NFTA_CT_DIRECTION]);
switch (priv->dir) {
case IP_CT_DIR_ORIGINAL:
case IP_CT_DIR_REPLY:
break;
default:
return -EINVAL;
}
}
switch (priv->key) { switch (priv->key) {
case NFT_CT_STATE: case NFT_CT_STATE:
case NFT_CT_DIRECTION: case NFT_CT_DIRECTION:
...@@ -262,22 +254,34 @@ static int nft_ct_init_validate_get(const struct nft_expr *expr, ...@@ -262,22 +254,34 @@ static int nft_ct_init_validate_get(const struct nft_expr *expr,
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
return 0; if (tb[NFTA_CT_DIRECTION] != NULL) {
} priv->dir = nla_get_u8(tb[NFTA_CT_DIRECTION]);
switch (priv->dir) {
static int nft_ct_init_validate_set(uint32_t key) case IP_CT_DIR_ORIGINAL:
{ case IP_CT_DIR_REPLY:
switch (key) {
case NFT_CT_MARK:
break; break;
default: default:
return -EOPNOTSUPP; return -EINVAL;
} }
}
priv->dreg = ntohl(nla_get_be32(tb[NFTA_CT_DREG]));
err = nft_validate_output_register(priv->dreg);
if (err < 0)
return err;
err = nft_validate_data_load(ctx, priv->dreg, NULL, NFT_DATA_VALUE);
if (err < 0)
return err;
err = nft_ct_l3proto_try_module_get(ctx->afi->family);
if (err < 0)
return err;
return 0; return 0;
} }
static int nft_ct_init(const struct nft_ctx *ctx, static int nft_ct_set_init(const struct nft_ctx *ctx,
const struct nft_expr *expr, const struct nft_expr *expr,
const struct nlattr * const tb[]) const struct nlattr * const tb[])
{ {
...@@ -285,31 +289,19 @@ static int nft_ct_init(const struct nft_ctx *ctx, ...@@ -285,31 +289,19 @@ static int nft_ct_init(const struct nft_ctx *ctx,
int err; int err;
priv->key = ntohl(nla_get_be32(tb[NFTA_CT_KEY])); priv->key = ntohl(nla_get_be32(tb[NFTA_CT_KEY]));
switch (priv->key) {
if (tb[NFTA_CT_DREG]) { #ifdef CONFIG_NF_CONNTRACK_MARK
err = nft_ct_init_validate_get(expr, tb); case NFT_CT_MARK:
if (err < 0) break;
return err; #endif
default:
priv->dreg = ntohl(nla_get_be32(tb[NFTA_CT_DREG])); return -EOPNOTSUPP;
err = nft_validate_output_register(priv->dreg); }
if (err < 0)
return err;
err = nft_validate_data_load(ctx, priv->dreg, NULL,
NFT_DATA_VALUE);
if (err < 0)
return err;
} else {
err = nft_ct_init_validate_set(priv->key);
if (err < 0)
return err;
priv->sreg = ntohl(nla_get_be32(tb[NFTA_CT_SREG])); priv->sreg = ntohl(nla_get_be32(tb[NFTA_CT_SREG]));
err = nft_validate_input_register(priv->sreg); err = nft_validate_input_register(priv->sreg);
if (err < 0) if (err < 0)
return err; return err;
}
err = nft_ct_l3proto_try_module_get(ctx->afi->family); err = nft_ct_l3proto_try_module_get(ctx->afi->family);
if (err < 0) if (err < 0)
...@@ -370,7 +362,7 @@ static const struct nft_expr_ops nft_ct_get_ops = { ...@@ -370,7 +362,7 @@ static const struct nft_expr_ops nft_ct_get_ops = {
.type = &nft_ct_type, .type = &nft_ct_type,
.size = NFT_EXPR_SIZE(sizeof(struct nft_ct)), .size = NFT_EXPR_SIZE(sizeof(struct nft_ct)),
.eval = nft_ct_get_eval, .eval = nft_ct_get_eval,
.init = nft_ct_init, .init = nft_ct_get_init,
.destroy = nft_ct_destroy, .destroy = nft_ct_destroy,
.dump = nft_ct_get_dump, .dump = nft_ct_get_dump,
}; };
...@@ -379,7 +371,7 @@ static const struct nft_expr_ops nft_ct_set_ops = { ...@@ -379,7 +371,7 @@ static const struct nft_expr_ops nft_ct_set_ops = {
.type = &nft_ct_type, .type = &nft_ct_type,
.size = NFT_EXPR_SIZE(sizeof(struct nft_ct)), .size = NFT_EXPR_SIZE(sizeof(struct nft_ct)),
.eval = nft_ct_set_eval, .eval = nft_ct_set_eval,
.init = nft_ct_init, .init = nft_ct_set_init,
.destroy = nft_ct_destroy, .destroy = nft_ct_destroy,
.dump = nft_ct_set_dump, .dump = nft_ct_set_dump,
}; };
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/log2.h>
#include <linux/jhash.h> #include <linux/jhash.h>
#include <linux/netlink.h> #include <linux/netlink.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
...@@ -19,7 +20,7 @@ ...@@ -19,7 +20,7 @@
#include <linux/netfilter/nf_tables.h> #include <linux/netfilter/nf_tables.h>
#include <net/netfilter/nf_tables.h> #include <net/netfilter/nf_tables.h>
#define NFT_HASH_MIN_SIZE 4 #define NFT_HASH_MIN_SIZE 4UL
struct nft_hash { struct nft_hash {
struct nft_hash_table __rcu *tbl; struct nft_hash_table __rcu *tbl;
...@@ -27,7 +28,6 @@ struct nft_hash { ...@@ -27,7 +28,6 @@ struct nft_hash {
struct nft_hash_table { struct nft_hash_table {
unsigned int size; unsigned int size;
unsigned int elements;
struct nft_hash_elem __rcu *buckets[]; struct nft_hash_elem __rcu *buckets[];
}; };
...@@ -82,6 +82,11 @@ static void nft_hash_tbl_free(const struct nft_hash_table *tbl) ...@@ -82,6 +82,11 @@ static void nft_hash_tbl_free(const struct nft_hash_table *tbl)
kfree(tbl); kfree(tbl);
} }
static unsigned int nft_hash_tbl_size(unsigned int nelem)
{
return max(roundup_pow_of_two(nelem * 4 / 3), NFT_HASH_MIN_SIZE);
}
static struct nft_hash_table *nft_hash_tbl_alloc(unsigned int nbuckets) static struct nft_hash_table *nft_hash_tbl_alloc(unsigned int nbuckets)
{ {
struct nft_hash_table *tbl; struct nft_hash_table *tbl;
...@@ -161,7 +166,6 @@ static int nft_hash_tbl_expand(const struct nft_set *set, struct nft_hash *priv) ...@@ -161,7 +166,6 @@ static int nft_hash_tbl_expand(const struct nft_set *set, struct nft_hash *priv)
break; break;
} }
} }
ntbl->elements = tbl->elements;
/* Publish new table */ /* Publish new table */
rcu_assign_pointer(priv->tbl, ntbl); rcu_assign_pointer(priv->tbl, ntbl);
...@@ -201,7 +205,6 @@ static int nft_hash_tbl_shrink(const struct nft_set *set, struct nft_hash *priv) ...@@ -201,7 +205,6 @@ static int nft_hash_tbl_shrink(const struct nft_set *set, struct nft_hash *priv)
; ;
RCU_INIT_POINTER(*pprev, tbl->buckets[i + ntbl->size]); RCU_INIT_POINTER(*pprev, tbl->buckets[i + ntbl->size]);
} }
ntbl->elements = tbl->elements;
/* Publish new table */ /* Publish new table */
rcu_assign_pointer(priv->tbl, ntbl); rcu_assign_pointer(priv->tbl, ntbl);
...@@ -237,10 +240,9 @@ static int nft_hash_insert(const struct nft_set *set, ...@@ -237,10 +240,9 @@ static int nft_hash_insert(const struct nft_set *set,
h = nft_hash_data(&he->key, tbl->size, set->klen); h = nft_hash_data(&he->key, tbl->size, set->klen);
RCU_INIT_POINTER(he->next, tbl->buckets[h]); RCU_INIT_POINTER(he->next, tbl->buckets[h]);
rcu_assign_pointer(tbl->buckets[h], he); rcu_assign_pointer(tbl->buckets[h], he);
tbl->elements++;
/* Expand table when exceeding 75% load */ /* Expand table when exceeding 75% load */
if (tbl->elements > tbl->size / 4 * 3) if (set->nelems + 1 > tbl->size / 4 * 3)
nft_hash_tbl_expand(set, priv); nft_hash_tbl_expand(set, priv);
return 0; return 0;
...@@ -268,10 +270,9 @@ static void nft_hash_remove(const struct nft_set *set, ...@@ -268,10 +270,9 @@ static void nft_hash_remove(const struct nft_set *set,
RCU_INIT_POINTER(*pprev, he->next); RCU_INIT_POINTER(*pprev, he->next);
synchronize_rcu(); synchronize_rcu();
kfree(he); kfree(he);
tbl->elements--;
/* Shrink table beneath 30% load */ /* Shrink table beneath 30% load */
if (tbl->elements < tbl->size * 3 / 10 && if (set->nelems - 1 < tbl->size * 3 / 10 &&
tbl->size > NFT_HASH_MIN_SIZE) tbl->size > NFT_HASH_MIN_SIZE)
nft_hash_tbl_shrink(set, priv); nft_hash_tbl_shrink(set, priv);
} }
...@@ -335,17 +336,23 @@ static unsigned int nft_hash_privsize(const struct nlattr * const nla[]) ...@@ -335,17 +336,23 @@ static unsigned int nft_hash_privsize(const struct nlattr * const nla[])
} }
static int nft_hash_init(const struct nft_set *set, static int nft_hash_init(const struct nft_set *set,
const struct nft_set_desc *desc,
const struct nlattr * const tb[]) const struct nlattr * const tb[])
{ {
struct nft_hash *priv = nft_set_priv(set); struct nft_hash *priv = nft_set_priv(set);
struct nft_hash_table *tbl; struct nft_hash_table *tbl;
unsigned int size;
if (unlikely(!nft_hash_rnd_initted)) { if (unlikely(!nft_hash_rnd_initted)) {
get_random_bytes(&nft_hash_rnd, 4); get_random_bytes(&nft_hash_rnd, 4);
nft_hash_rnd_initted = true; nft_hash_rnd_initted = true;
} }
tbl = nft_hash_tbl_alloc(NFT_HASH_MIN_SIZE); size = NFT_HASH_MIN_SIZE;
if (desc->size)
size = nft_hash_tbl_size(desc->size);
tbl = nft_hash_tbl_alloc(size);
if (tbl == NULL) if (tbl == NULL)
return -ENOMEM; return -ENOMEM;
RCU_INIT_POINTER(priv->tbl, tbl); RCU_INIT_POINTER(priv->tbl, tbl);
...@@ -369,8 +376,37 @@ static void nft_hash_destroy(const struct nft_set *set) ...@@ -369,8 +376,37 @@ static void nft_hash_destroy(const struct nft_set *set)
kfree(tbl); kfree(tbl);
} }
static bool nft_hash_estimate(const struct nft_set_desc *desc, u32 features,
struct nft_set_estimate *est)
{
unsigned int esize;
esize = sizeof(struct nft_hash_elem);
if (features & NFT_SET_MAP)
esize += FIELD_SIZEOF(struct nft_hash_elem, data[0]);
if (desc->size) {
est->size = sizeof(struct nft_hash) +
nft_hash_tbl_size(desc->size) *
sizeof(struct nft_hash_elem *) +
desc->size * esize;
} else {
/* Resizing happens when the load drops below 30% or goes
* above 75%. The average of 52.5% load (approximated by 50%)
* is used for the size estimation of the hash buckets,
* meaning we calculate two buckets per element.
*/
est->size = esize + 2 * sizeof(struct nft_hash_elem *);
}
est->class = NFT_SET_CLASS_O_1;
return true;
}
static struct nft_set_ops nft_hash_ops __read_mostly = { static struct nft_set_ops nft_hash_ops __read_mostly = {
.privsize = nft_hash_privsize, .privsize = nft_hash_privsize,
.estimate = nft_hash_estimate,
.init = nft_hash_init, .init = nft_hash_init,
.destroy = nft_hash_destroy, .destroy = nft_hash_destroy,
.get = nft_hash_get, .get = nft_hash_get,
......
...@@ -56,8 +56,14 @@ static int nft_lookup_init(const struct nft_ctx *ctx, ...@@ -56,8 +56,14 @@ static int nft_lookup_init(const struct nft_ctx *ctx,
return -EINVAL; return -EINVAL;
set = nf_tables_set_lookup(ctx->table, tb[NFTA_LOOKUP_SET]); set = nf_tables_set_lookup(ctx->table, tb[NFTA_LOOKUP_SET]);
if (IS_ERR(set)) {
if (tb[NFTA_LOOKUP_SET_ID]) {
set = nf_tables_set_lookup_byid(ctx->net,
tb[NFTA_LOOKUP_SET_ID]);
}
if (IS_ERR(set)) if (IS_ERR(set))
return PTR_ERR(set); return PTR_ERR(set);
}
priv->sreg = ntohl(nla_get_be32(tb[NFTA_LOOKUP_SREG])); priv->sreg = ntohl(nla_get_be32(tb[NFTA_LOOKUP_SREG]));
err = nft_validate_input_register(priv->sreg); err = nft_validate_input_register(priv->sreg);
......
...@@ -18,16 +18,9 @@ ...@@ -18,16 +18,9 @@
#include <net/sock.h> #include <net/sock.h>
#include <net/tcp_states.h> /* for TCP_TIME_WAIT */ #include <net/tcp_states.h> /* for TCP_TIME_WAIT */
#include <net/netfilter/nf_tables.h> #include <net/netfilter/nf_tables.h>
#include <net/netfilter/nft_meta.h>
struct nft_meta { void nft_meta_get_eval(const struct nft_expr *expr,
enum nft_meta_keys key:8;
union {
enum nft_registers dreg:8;
enum nft_registers sreg:8;
};
};
static void nft_meta_get_eval(const struct nft_expr *expr,
struct nft_data data[NFT_REG_MAX + 1], struct nft_data data[NFT_REG_MAX + 1],
const struct nft_pktinfo *pkt) const struct nft_pktinfo *pkt)
{ {
...@@ -140,8 +133,9 @@ static void nft_meta_get_eval(const struct nft_expr *expr, ...@@ -140,8 +133,9 @@ static void nft_meta_get_eval(const struct nft_expr *expr,
err: err:
data[NFT_REG_VERDICT].verdict = NFT_BREAK; data[NFT_REG_VERDICT].verdict = NFT_BREAK;
} }
EXPORT_SYMBOL_GPL(nft_meta_get_eval);
static void nft_meta_set_eval(const struct nft_expr *expr, void nft_meta_set_eval(const struct nft_expr *expr,
struct nft_data data[NFT_REG_MAX + 1], struct nft_data data[NFT_REG_MAX + 1],
const struct nft_pktinfo *pkt) const struct nft_pktinfo *pkt)
{ {
...@@ -163,28 +157,24 @@ static void nft_meta_set_eval(const struct nft_expr *expr, ...@@ -163,28 +157,24 @@ static void nft_meta_set_eval(const struct nft_expr *expr,
WARN_ON(1); WARN_ON(1);
} }
} }
EXPORT_SYMBOL_GPL(nft_meta_set_eval);
static const struct nla_policy nft_meta_policy[NFTA_META_MAX + 1] = { const struct nla_policy nft_meta_policy[NFTA_META_MAX + 1] = {
[NFTA_META_DREG] = { .type = NLA_U32 }, [NFTA_META_DREG] = { .type = NLA_U32 },
[NFTA_META_KEY] = { .type = NLA_U32 }, [NFTA_META_KEY] = { .type = NLA_U32 },
[NFTA_META_SREG] = { .type = NLA_U32 }, [NFTA_META_SREG] = { .type = NLA_U32 },
}; };
EXPORT_SYMBOL_GPL(nft_meta_policy);
static int nft_meta_init_validate_set(uint32_t key) int nft_meta_get_init(const struct nft_ctx *ctx,
const struct nft_expr *expr,
const struct nlattr * const tb[])
{ {
switch (key) { struct nft_meta *priv = nft_expr_priv(expr);
case NFT_META_MARK: int err;
case NFT_META_PRIORITY:
case NFT_META_NFTRACE:
return 0;
default:
return -EOPNOTSUPP;
}
}
static int nft_meta_init_validate_get(uint32_t key) priv->key = ntohl(nla_get_be32(tb[NFTA_META_KEY]));
{ switch (priv->key) {
switch (key) {
case NFT_META_LEN: case NFT_META_LEN:
case NFT_META_PROTOCOL: case NFT_META_PROTOCOL:
case NFT_META_NFPROTO: case NFT_META_NFPROTO:
...@@ -205,39 +195,41 @@ static int nft_meta_init_validate_get(uint32_t key) ...@@ -205,39 +195,41 @@ static int nft_meta_init_validate_get(uint32_t key)
#ifdef CONFIG_NETWORK_SECMARK #ifdef CONFIG_NETWORK_SECMARK
case NFT_META_SECMARK: case NFT_META_SECMARK:
#endif #endif
return 0; break;
default: default:
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
priv->dreg = ntohl(nla_get_be32(tb[NFTA_META_DREG]));
err = nft_validate_output_register(priv->dreg);
if (err < 0)
return err;
err = nft_validate_data_load(ctx, priv->dreg, NULL, NFT_DATA_VALUE);
if (err < 0)
return err;
return 0;
} }
EXPORT_SYMBOL_GPL(nft_meta_get_init);
static int nft_meta_init(const struct nft_ctx *ctx, const struct nft_expr *expr, int nft_meta_set_init(const struct nft_ctx *ctx,
const struct nft_expr *expr,
const struct nlattr * const tb[]) const struct nlattr * const tb[])
{ {
struct nft_meta *priv = nft_expr_priv(expr); struct nft_meta *priv = nft_expr_priv(expr);
int err; int err;
priv->key = ntohl(nla_get_be32(tb[NFTA_META_KEY])); priv->key = ntohl(nla_get_be32(tb[NFTA_META_KEY]));
switch (priv->key) {
if (tb[NFTA_META_DREG]) { case NFT_META_MARK:
err = nft_meta_init_validate_get(priv->key); case NFT_META_PRIORITY:
if (err < 0) case NFT_META_NFTRACE:
return err; break;
default:
priv->dreg = ntohl(nla_get_be32(tb[NFTA_META_DREG])); return -EOPNOTSUPP;
err = nft_validate_output_register(priv->dreg);
if (err < 0)
return err;
return nft_validate_data_load(ctx, priv->dreg, NULL,
NFT_DATA_VALUE);
} }
err = nft_meta_init_validate_set(priv->key);
if (err < 0)
return err;
priv->sreg = ntohl(nla_get_be32(tb[NFTA_META_SREG])); priv->sreg = ntohl(nla_get_be32(tb[NFTA_META_SREG]));
err = nft_validate_input_register(priv->sreg); err = nft_validate_input_register(priv->sreg);
if (err < 0) if (err < 0)
...@@ -245,8 +237,9 @@ static int nft_meta_init(const struct nft_ctx *ctx, const struct nft_expr *expr, ...@@ -245,8 +237,9 @@ static int nft_meta_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(nft_meta_set_init);
static int nft_meta_get_dump(struct sk_buff *skb, int nft_meta_get_dump(struct sk_buff *skb,
const struct nft_expr *expr) const struct nft_expr *expr)
{ {
const struct nft_meta *priv = nft_expr_priv(expr); const struct nft_meta *priv = nft_expr_priv(expr);
...@@ -260,8 +253,9 @@ static int nft_meta_get_dump(struct sk_buff *skb, ...@@ -260,8 +253,9 @@ static int nft_meta_get_dump(struct sk_buff *skb,
nla_put_failure: nla_put_failure:
return -1; return -1;
} }
EXPORT_SYMBOL_GPL(nft_meta_get_dump);
static int nft_meta_set_dump(struct sk_buff *skb, int nft_meta_set_dump(struct sk_buff *skb,
const struct nft_expr *expr) const struct nft_expr *expr)
{ {
const struct nft_meta *priv = nft_expr_priv(expr); const struct nft_meta *priv = nft_expr_priv(expr);
...@@ -276,13 +270,14 @@ static int nft_meta_set_dump(struct sk_buff *skb, ...@@ -276,13 +270,14 @@ static int nft_meta_set_dump(struct sk_buff *skb,
nla_put_failure: nla_put_failure:
return -1; return -1;
} }
EXPORT_SYMBOL_GPL(nft_meta_set_dump);
static struct nft_expr_type nft_meta_type; static struct nft_expr_type nft_meta_type;
static const struct nft_expr_ops nft_meta_get_ops = { static const struct nft_expr_ops nft_meta_get_ops = {
.type = &nft_meta_type, .type = &nft_meta_type,
.size = NFT_EXPR_SIZE(sizeof(struct nft_meta)), .size = NFT_EXPR_SIZE(sizeof(struct nft_meta)),
.eval = nft_meta_get_eval, .eval = nft_meta_get_eval,
.init = nft_meta_init, .init = nft_meta_get_init,
.dump = nft_meta_get_dump, .dump = nft_meta_get_dump,
}; };
...@@ -290,7 +285,7 @@ static const struct nft_expr_ops nft_meta_set_ops = { ...@@ -290,7 +285,7 @@ static const struct nft_expr_ops nft_meta_set_ops = {
.type = &nft_meta_type, .type = &nft_meta_type,
.size = NFT_EXPR_SIZE(sizeof(struct nft_meta)), .size = NFT_EXPR_SIZE(sizeof(struct nft_meta)),
.eval = nft_meta_set_eval, .eval = nft_meta_set_eval,
.init = nft_meta_init, .init = nft_meta_set_init,
.dump = nft_meta_set_dump, .dump = nft_meta_set_dump,
}; };
......
...@@ -201,6 +201,7 @@ static unsigned int nft_rbtree_privsize(const struct nlattr * const nla[]) ...@@ -201,6 +201,7 @@ static unsigned int nft_rbtree_privsize(const struct nlattr * const nla[])
} }
static int nft_rbtree_init(const struct nft_set *set, static int nft_rbtree_init(const struct nft_set *set,
const struct nft_set_desc *desc,
const struct nlattr * const nla[]) const struct nlattr * const nla[])
{ {
struct nft_rbtree *priv = nft_set_priv(set); struct nft_rbtree *priv = nft_set_priv(set);
...@@ -222,8 +223,28 @@ static void nft_rbtree_destroy(const struct nft_set *set) ...@@ -222,8 +223,28 @@ static void nft_rbtree_destroy(const struct nft_set *set)
} }
} }
static bool nft_rbtree_estimate(const struct nft_set_desc *desc, u32 features,
struct nft_set_estimate *est)
{
unsigned int nsize;
nsize = sizeof(struct nft_rbtree_elem);
if (features & NFT_SET_MAP)
nsize += FIELD_SIZEOF(struct nft_rbtree_elem, data[0]);
if (desc->size)
est->size = sizeof(struct nft_rbtree) + desc->size * nsize;
else
est->size = nsize;
est->class = NFT_SET_CLASS_O_LOG_N;
return true;
}
static struct nft_set_ops nft_rbtree_ops __read_mostly = { static struct nft_set_ops nft_rbtree_ops __read_mostly = {
.privsize = nft_rbtree_privsize, .privsize = nft_rbtree_privsize,
.estimate = nft_rbtree_estimate,
.init = nft_rbtree_init, .init = nft_rbtree_init,
.destroy = nft_rbtree_destroy, .destroy = nft_rbtree_destroy,
.insert = nft_rbtree_insert, .insert = nft_rbtree_insert,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment