|
@@ -16,8 +16,11 @@
|
|
|
#include <linux/tcp.h>
|
|
|
#include <linux/udp.h>
|
|
|
#include <linux/sctp.h>
|
|
|
+#include <linux/static_key.h>
|
|
|
#include <net/ip.h>
|
|
|
+#include <net/genetlink.h>
|
|
|
#include <net/netfilter/nf_conntrack_core.h>
|
|
|
+#include <net/netfilter/nf_conntrack_count.h>
|
|
|
#include <net/netfilter/nf_conntrack_helper.h>
|
|
|
#include <net/netfilter/nf_conntrack_labels.h>
|
|
|
#include <net/netfilter/nf_conntrack_seqadj.h>
|
|
@@ -76,6 +79,31 @@ struct ovs_conntrack_info {
|
|
|
#endif
|
|
|
};
|
|
|
|
|
|
+#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT)
|
|
|
+#define OVS_CT_LIMIT_UNLIMITED 0
|
|
|
+#define OVS_CT_LIMIT_DEFAULT OVS_CT_LIMIT_UNLIMITED
|
|
|
+#define CT_LIMIT_HASH_BUCKETS 512
|
|
|
+static DEFINE_STATIC_KEY_FALSE(ovs_ct_limit_enabled);
|
|
|
+
|
|
|
+struct ovs_ct_limit {
|
|
|
+ /* Elements in ovs_ct_limit_info->limits hash table */
|
|
|
+ struct hlist_node hlist_node;
|
|
|
+ struct rcu_head rcu;
|
|
|
+ u16 zone;
|
|
|
+ u32 limit;
|
|
|
+};
|
|
|
+
|
|
|
+struct ovs_ct_limit_info {
|
|
|
+ u32 default_limit;
|
|
|
+ struct hlist_head *limits;
|
|
|
+ struct nf_conncount_data *data;
|
|
|
+};
|
|
|
+
|
|
|
+static const struct nla_policy ct_limit_policy[OVS_CT_LIMIT_ATTR_MAX + 1] = {
|
|
|
+ [OVS_CT_LIMIT_ATTR_ZONE_LIMIT] = { .type = NLA_NESTED, },
|
|
|
+};
|
|
|
+#endif
|
|
|
+
|
|
|
static bool labels_nonzero(const struct ovs_key_ct_labels *labels);
|
|
|
|
|
|
static void __ovs_ct_free_action(struct ovs_conntrack_info *ct_info);
|
|
@@ -1036,6 +1064,89 @@ static bool labels_nonzero(const struct ovs_key_ct_labels *labels)
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT)
|
|
|
+static struct hlist_head *ct_limit_hash_bucket(
|
|
|
+ const struct ovs_ct_limit_info *info, u16 zone)
|
|
|
+{
|
|
|
+ return &info->limits[zone & (CT_LIMIT_HASH_BUCKETS - 1)];
|
|
|
+}
|
|
|
+
|
|
|
+/* Call with ovs_mutex */
|
|
|
+static void ct_limit_set(const struct ovs_ct_limit_info *info,
|
|
|
+ struct ovs_ct_limit *new_ct_limit)
|
|
|
+{
|
|
|
+ struct ovs_ct_limit *ct_limit;
|
|
|
+ struct hlist_head *head;
|
|
|
+
|
|
|
+ head = ct_limit_hash_bucket(info, new_ct_limit->zone);
|
|
|
+ hlist_for_each_entry_rcu(ct_limit, head, hlist_node) {
|
|
|
+ if (ct_limit->zone == new_ct_limit->zone) {
|
|
|
+ hlist_replace_rcu(&ct_limit->hlist_node,
|
|
|
+ &new_ct_limit->hlist_node);
|
|
|
+ kfree_rcu(ct_limit, rcu);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ hlist_add_head_rcu(&new_ct_limit->hlist_node, head);
|
|
|
+}
|
|
|
+
|
|
|
+/* Call with ovs_mutex */
|
|
|
+static void ct_limit_del(const struct ovs_ct_limit_info *info, u16 zone)
|
|
|
+{
|
|
|
+ struct ovs_ct_limit *ct_limit;
|
|
|
+ struct hlist_head *head;
|
|
|
+ struct hlist_node *n;
|
|
|
+
|
|
|
+ head = ct_limit_hash_bucket(info, zone);
|
|
|
+ hlist_for_each_entry_safe(ct_limit, n, head, hlist_node) {
|
|
|
+ if (ct_limit->zone == zone) {
|
|
|
+ hlist_del_rcu(&ct_limit->hlist_node);
|
|
|
+ kfree_rcu(ct_limit, rcu);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* Call with RCU read lock */
|
|
|
+static u32 ct_limit_get(const struct ovs_ct_limit_info *info, u16 zone)
|
|
|
+{
|
|
|
+ struct ovs_ct_limit *ct_limit;
|
|
|
+ struct hlist_head *head;
|
|
|
+
|
|
|
+ head = ct_limit_hash_bucket(info, zone);
|
|
|
+ hlist_for_each_entry_rcu(ct_limit, head, hlist_node) {
|
|
|
+ if (ct_limit->zone == zone)
|
|
|
+ return ct_limit->limit;
|
|
|
+ }
|
|
|
+
|
|
|
+ return info->default_limit;
|
|
|
+}
|
|
|
+
|
|
|
+static int ovs_ct_check_limit(struct net *net,
|
|
|
+ const struct ovs_conntrack_info *info,
|
|
|
+ const struct nf_conntrack_tuple *tuple)
|
|
|
+{
|
|
|
+ struct ovs_net *ovs_net = net_generic(net, ovs_net_id);
|
|
|
+ const struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info;
|
|
|
+ u32 per_zone_limit, connections;
|
|
|
+ u32 conncount_key;
|
|
|
+
|
|
|
+ conncount_key = info->zone.id;
|
|
|
+
|
|
|
+ per_zone_limit = ct_limit_get(ct_limit_info, info->zone.id);
|
|
|
+ if (per_zone_limit == OVS_CT_LIMIT_UNLIMITED)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ connections = nf_conncount_count(net, ct_limit_info->data,
|
|
|
+ &conncount_key, tuple, &info->zone);
|
|
|
+ if (connections > per_zone_limit)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
/* Lookup connection and confirm if unconfirmed. */
|
|
|
static int ovs_ct_commit(struct net *net, struct sw_flow_key *key,
|
|
|
const struct ovs_conntrack_info *info,
|
|
@@ -1054,6 +1165,21 @@ static int ovs_ct_commit(struct net *net, struct sw_flow_key *key,
|
|
|
if (!ct)
|
|
|
return 0;
|
|
|
|
|
|
+#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT)
|
|
|
+ if (static_branch_unlikely(&ovs_ct_limit_enabled)) {
|
|
|
+ if (!nf_ct_is_confirmed(ct)) {
|
|
|
+ err = ovs_ct_check_limit(net, info,
|
|
|
+ &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
|
|
|
+ if (err) {
|
|
|
+ net_warn_ratelimited("openvswitch: zone: %u "
|
|
|
+ "execeeds conntrack limit\n",
|
|
|
+ info->zone.id);
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
/* Set the conntrack event mask if given. NEW and DELETE events have
|
|
|
* their own groups, but the NFNLGRP_CONNTRACK_UPDATE group listener
|
|
|
* typically would receive many kinds of updates. Setting the event
|
|
@@ -1655,7 +1781,420 @@ static void __ovs_ct_free_action(struct ovs_conntrack_info *ct_info)
|
|
|
nf_ct_tmpl_free(ct_info->ct);
|
|
|
}
|
|
|
|
|
|
-void ovs_ct_init(struct net *net)
|
|
|
+#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT)
|
|
|
+static int ovs_ct_limit_init(struct net *net, struct ovs_net *ovs_net)
|
|
|
+{
|
|
|
+ int i, err;
|
|
|
+
|
|
|
+ ovs_net->ct_limit_info = kmalloc(sizeof(*ovs_net->ct_limit_info),
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!ovs_net->ct_limit_info)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ ovs_net->ct_limit_info->default_limit = OVS_CT_LIMIT_DEFAULT;
|
|
|
+ ovs_net->ct_limit_info->limits =
|
|
|
+ kmalloc_array(CT_LIMIT_HASH_BUCKETS, sizeof(struct hlist_head),
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!ovs_net->ct_limit_info->limits) {
|
|
|
+ kfree(ovs_net->ct_limit_info);
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < CT_LIMIT_HASH_BUCKETS; i++)
|
|
|
+ INIT_HLIST_HEAD(&ovs_net->ct_limit_info->limits[i]);
|
|
|
+
|
|
|
+ ovs_net->ct_limit_info->data =
|
|
|
+ nf_conncount_init(net, NFPROTO_INET, sizeof(u32));
|
|
|
+
|
|
|
+ if (IS_ERR(ovs_net->ct_limit_info->data)) {
|
|
|
+ err = PTR_ERR(ovs_net->ct_limit_info->data);
|
|
|
+ kfree(ovs_net->ct_limit_info->limits);
|
|
|
+ kfree(ovs_net->ct_limit_info);
|
|
|
+ pr_err("openvswitch: failed to init nf_conncount %d\n", err);
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void ovs_ct_limit_exit(struct net *net, struct ovs_net *ovs_net)
|
|
|
+{
|
|
|
+ const struct ovs_ct_limit_info *info = ovs_net->ct_limit_info;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ nf_conncount_destroy(net, NFPROTO_INET, info->data);
|
|
|
+ for (i = 0; i < CT_LIMIT_HASH_BUCKETS; ++i) {
|
|
|
+ struct hlist_head *head = &info->limits[i];
|
|
|
+ struct ovs_ct_limit *ct_limit;
|
|
|
+
|
|
|
+ hlist_for_each_entry_rcu(ct_limit, head, hlist_node)
|
|
|
+ kfree_rcu(ct_limit, rcu);
|
|
|
+ }
|
|
|
+ kfree(ovs_net->ct_limit_info->limits);
|
|
|
+ kfree(ovs_net->ct_limit_info);
|
|
|
+}
|
|
|
+
|
|
|
+static struct sk_buff *
|
|
|
+ovs_ct_limit_cmd_reply_start(struct genl_info *info, u8 cmd,
|
|
|
+ struct ovs_header **ovs_reply_header)
|
|
|
+{
|
|
|
+ struct ovs_header *ovs_header = info->userhdr;
|
|
|
+ struct sk_buff *skb;
|
|
|
+
|
|
|
+ skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
|
|
+ if (!skb)
|
|
|
+ return ERR_PTR(-ENOMEM);
|
|
|
+
|
|
|
+ *ovs_reply_header = genlmsg_put(skb, info->snd_portid,
|
|
|
+ info->snd_seq,
|
|
|
+ &dp_ct_limit_genl_family, 0, cmd);
|
|
|
+
|
|
|
+ if (!*ovs_reply_header) {
|
|
|
+ nlmsg_free(skb);
|
|
|
+ return ERR_PTR(-EMSGSIZE);
|
|
|
+ }
|
|
|
+ (*ovs_reply_header)->dp_ifindex = ovs_header->dp_ifindex;
|
|
|
+
|
|
|
+ return skb;
|
|
|
+}
|
|
|
+
|
|
|
+static bool check_zone_id(int zone_id, u16 *pzone)
|
|
|
+{
|
|
|
+ if (zone_id >= 0 && zone_id <= 65535) {
|
|
|
+ *pzone = (u16)zone_id;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit,
|
|
|
+ struct ovs_ct_limit_info *info)
|
|
|
+{
|
|
|
+ struct ovs_zone_limit *zone_limit;
|
|
|
+ int rem;
|
|
|
+ u16 zone;
|
|
|
+
|
|
|
+ rem = NLA_ALIGN(nla_len(nla_zone_limit));
|
|
|
+ zone_limit = (struct ovs_zone_limit *)nla_data(nla_zone_limit);
|
|
|
+
|
|
|
+ while (rem >= sizeof(*zone_limit)) {
|
|
|
+ if (unlikely(zone_limit->zone_id ==
|
|
|
+ OVS_ZONE_LIMIT_DEFAULT_ZONE)) {
|
|
|
+ ovs_lock();
|
|
|
+ info->default_limit = zone_limit->limit;
|
|
|
+ ovs_unlock();
|
|
|
+ } else if (unlikely(!check_zone_id(
|
|
|
+ zone_limit->zone_id, &zone))) {
|
|
|
+ OVS_NLERR(true, "zone id is out of range");
|
|
|
+ } else {
|
|
|
+ struct ovs_ct_limit *ct_limit;
|
|
|
+
|
|
|
+ ct_limit = kmalloc(sizeof(*ct_limit), GFP_KERNEL);
|
|
|
+ if (!ct_limit)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ ct_limit->zone = zone;
|
|
|
+ ct_limit->limit = zone_limit->limit;
|
|
|
+
|
|
|
+ ovs_lock();
|
|
|
+ ct_limit_set(info, ct_limit);
|
|
|
+ ovs_unlock();
|
|
|
+ }
|
|
|
+ rem -= NLA_ALIGN(sizeof(*zone_limit));
|
|
|
+ zone_limit = (struct ovs_zone_limit *)((u8 *)zone_limit +
|
|
|
+ NLA_ALIGN(sizeof(*zone_limit)));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rem)
|
|
|
+ OVS_NLERR(true, "set zone limit has %d unknown bytes", rem);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int ovs_ct_limit_del_zone_limit(struct nlattr *nla_zone_limit,
|
|
|
+ struct ovs_ct_limit_info *info)
|
|
|
+{
|
|
|
+ struct ovs_zone_limit *zone_limit;
|
|
|
+ int rem;
|
|
|
+ u16 zone;
|
|
|
+
|
|
|
+ rem = NLA_ALIGN(nla_len(nla_zone_limit));
|
|
|
+ zone_limit = (struct ovs_zone_limit *)nla_data(nla_zone_limit);
|
|
|
+
|
|
|
+ while (rem >= sizeof(*zone_limit)) {
|
|
|
+ if (unlikely(zone_limit->zone_id ==
|
|
|
+ OVS_ZONE_LIMIT_DEFAULT_ZONE)) {
|
|
|
+ ovs_lock();
|
|
|
+ info->default_limit = OVS_CT_LIMIT_DEFAULT;
|
|
|
+ ovs_unlock();
|
|
|
+ } else if (unlikely(!check_zone_id(
|
|
|
+ zone_limit->zone_id, &zone))) {
|
|
|
+ OVS_NLERR(true, "zone id is out of range");
|
|
|
+ } else {
|
|
|
+ ovs_lock();
|
|
|
+ ct_limit_del(info, zone);
|
|
|
+ ovs_unlock();
|
|
|
+ }
|
|
|
+ rem -= NLA_ALIGN(sizeof(*zone_limit));
|
|
|
+ zone_limit = (struct ovs_zone_limit *)((u8 *)zone_limit +
|
|
|
+ NLA_ALIGN(sizeof(*zone_limit)));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rem)
|
|
|
+ OVS_NLERR(true, "del zone limit has %d unknown bytes", rem);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int ovs_ct_limit_get_default_limit(struct ovs_ct_limit_info *info,
|
|
|
+ struct sk_buff *reply)
|
|
|
+{
|
|
|
+ struct ovs_zone_limit zone_limit;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ zone_limit.zone_id = OVS_ZONE_LIMIT_DEFAULT_ZONE;
|
|
|
+ zone_limit.limit = info->default_limit;
|
|
|
+ err = nla_put_nohdr(reply, sizeof(zone_limit), &zone_limit);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int __ovs_ct_limit_get_zone_limit(struct net *net,
|
|
|
+ struct nf_conncount_data *data,
|
|
|
+ u16 zone_id, u32 limit,
|
|
|
+ struct sk_buff *reply)
|
|
|
+{
|
|
|
+ struct nf_conntrack_zone ct_zone;
|
|
|
+ struct ovs_zone_limit zone_limit;
|
|
|
+ u32 conncount_key = zone_id;
|
|
|
+
|
|
|
+ zone_limit.zone_id = zone_id;
|
|
|
+ zone_limit.limit = limit;
|
|
|
+ nf_ct_zone_init(&ct_zone, zone_id, NF_CT_DEFAULT_ZONE_DIR, 0);
|
|
|
+
|
|
|
+ zone_limit.count = nf_conncount_count(net, data, &conncount_key, NULL,
|
|
|
+ &ct_zone);
|
|
|
+ return nla_put_nohdr(reply, sizeof(zone_limit), &zone_limit);
|
|
|
+}
|
|
|
+
|
|
|
+static int ovs_ct_limit_get_zone_limit(struct net *net,
|
|
|
+ struct nlattr *nla_zone_limit,
|
|
|
+ struct ovs_ct_limit_info *info,
|
|
|
+ struct sk_buff *reply)
|
|
|
+{
|
|
|
+ struct ovs_zone_limit *zone_limit;
|
|
|
+ int rem, err;
|
|
|
+ u32 limit;
|
|
|
+ u16 zone;
|
|
|
+
|
|
|
+ rem = NLA_ALIGN(nla_len(nla_zone_limit));
|
|
|
+ zone_limit = (struct ovs_zone_limit *)nla_data(nla_zone_limit);
|
|
|
+
|
|
|
+ while (rem >= sizeof(*zone_limit)) {
|
|
|
+ if (unlikely(zone_limit->zone_id ==
|
|
|
+ OVS_ZONE_LIMIT_DEFAULT_ZONE)) {
|
|
|
+ err = ovs_ct_limit_get_default_limit(info, reply);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ } else if (unlikely(!check_zone_id(zone_limit->zone_id,
|
|
|
+ &zone))) {
|
|
|
+ OVS_NLERR(true, "zone id is out of range");
|
|
|
+ } else {
|
|
|
+ rcu_read_lock();
|
|
|
+ limit = ct_limit_get(info, zone);
|
|
|
+ rcu_read_unlock();
|
|
|
+
|
|
|
+ err = __ovs_ct_limit_get_zone_limit(
|
|
|
+ net, info->data, zone, limit, reply);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+ rem -= NLA_ALIGN(sizeof(*zone_limit));
|
|
|
+ zone_limit = (struct ovs_zone_limit *)((u8 *)zone_limit +
|
|
|
+ NLA_ALIGN(sizeof(*zone_limit)));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rem)
|
|
|
+ OVS_NLERR(true, "get zone limit has %d unknown bytes", rem);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int ovs_ct_limit_get_all_zone_limit(struct net *net,
|
|
|
+ struct ovs_ct_limit_info *info,
|
|
|
+ struct sk_buff *reply)
|
|
|
+{
|
|
|
+ struct ovs_ct_limit *ct_limit;
|
|
|
+ struct hlist_head *head;
|
|
|
+ int i, err = 0;
|
|
|
+
|
|
|
+ err = ovs_ct_limit_get_default_limit(info, reply);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ rcu_read_lock();
|
|
|
+ for (i = 0; i < CT_LIMIT_HASH_BUCKETS; ++i) {
|
|
|
+ head = &info->limits[i];
|
|
|
+ hlist_for_each_entry_rcu(ct_limit, head, hlist_node) {
|
|
|
+ err = __ovs_ct_limit_get_zone_limit(net, info->data,
|
|
|
+ ct_limit->zone, ct_limit->limit, reply);
|
|
|
+ if (err)
|
|
|
+ goto exit_err;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+exit_err:
|
|
|
+ rcu_read_unlock();
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static int ovs_ct_limit_cmd_set(struct sk_buff *skb, struct genl_info *info)
|
|
|
+{
|
|
|
+ struct nlattr **a = info->attrs;
|
|
|
+ struct sk_buff *reply;
|
|
|
+ struct ovs_header *ovs_reply_header;
|
|
|
+ struct ovs_net *ovs_net = net_generic(sock_net(skb->sk), ovs_net_id);
|
|
|
+ struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_SET,
|
|
|
+ &ovs_reply_header);
|
|
|
+ if (IS_ERR(reply))
|
|
|
+ return PTR_ERR(reply);
|
|
|
+
|
|
|
+ if (!a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto exit_err;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = ovs_ct_limit_set_zone_limit(a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT],
|
|
|
+ ct_limit_info);
|
|
|
+ if (err)
|
|
|
+ goto exit_err;
|
|
|
+
|
|
|
+ static_branch_enable(&ovs_ct_limit_enabled);
|
|
|
+
|
|
|
+ genlmsg_end(reply, ovs_reply_header);
|
|
|
+ return genlmsg_reply(reply, info);
|
|
|
+
|
|
|
+exit_err:
|
|
|
+ nlmsg_free(reply);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static int ovs_ct_limit_cmd_del(struct sk_buff *skb, struct genl_info *info)
|
|
|
+{
|
|
|
+ struct nlattr **a = info->attrs;
|
|
|
+ struct sk_buff *reply;
|
|
|
+ struct ovs_header *ovs_reply_header;
|
|
|
+ struct ovs_net *ovs_net = net_generic(sock_net(skb->sk), ovs_net_id);
|
|
|
+ struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_DEL,
|
|
|
+ &ovs_reply_header);
|
|
|
+ if (IS_ERR(reply))
|
|
|
+ return PTR_ERR(reply);
|
|
|
+
|
|
|
+ if (!a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto exit_err;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = ovs_ct_limit_del_zone_limit(a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT],
|
|
|
+ ct_limit_info);
|
|
|
+ if (err)
|
|
|
+ goto exit_err;
|
|
|
+
|
|
|
+ genlmsg_end(reply, ovs_reply_header);
|
|
|
+ return genlmsg_reply(reply, info);
|
|
|
+
|
|
|
+exit_err:
|
|
|
+ nlmsg_free(reply);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static int ovs_ct_limit_cmd_get(struct sk_buff *skb, struct genl_info *info)
|
|
|
+{
|
|
|
+ struct nlattr **a = info->attrs;
|
|
|
+ struct nlattr *nla_reply;
|
|
|
+ struct sk_buff *reply;
|
|
|
+ struct ovs_header *ovs_reply_header;
|
|
|
+ struct net *net = sock_net(skb->sk);
|
|
|
+ struct ovs_net *ovs_net = net_generic(net, ovs_net_id);
|
|
|
+ struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_GET,
|
|
|
+ &ovs_reply_header);
|
|
|
+ if (IS_ERR(reply))
|
|
|
+ return PTR_ERR(reply);
|
|
|
+
|
|
|
+ nla_reply = nla_nest_start(reply, OVS_CT_LIMIT_ATTR_ZONE_LIMIT);
|
|
|
+
|
|
|
+ if (a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]) {
|
|
|
+ err = ovs_ct_limit_get_zone_limit(
|
|
|
+ net, a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT], ct_limit_info,
|
|
|
+ reply);
|
|
|
+ if (err)
|
|
|
+ goto exit_err;
|
|
|
+ } else {
|
|
|
+ err = ovs_ct_limit_get_all_zone_limit(net, ct_limit_info,
|
|
|
+ reply);
|
|
|
+ if (err)
|
|
|
+ goto exit_err;
|
|
|
+ }
|
|
|
+
|
|
|
+ nla_nest_end(reply, nla_reply);
|
|
|
+ genlmsg_end(reply, ovs_reply_header);
|
|
|
+ return genlmsg_reply(reply, info);
|
|
|
+
|
|
|
+exit_err:
|
|
|
+ nlmsg_free(reply);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static struct genl_ops ct_limit_genl_ops[] = {
|
|
|
+ { .cmd = OVS_CT_LIMIT_CMD_SET,
|
|
|
+ .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN
|
|
|
+ * privilege. */
|
|
|
+ .policy = ct_limit_policy,
|
|
|
+ .doit = ovs_ct_limit_cmd_set,
|
|
|
+ },
|
|
|
+ { .cmd = OVS_CT_LIMIT_CMD_DEL,
|
|
|
+ .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN
|
|
|
+ * privilege. */
|
|
|
+ .policy = ct_limit_policy,
|
|
|
+ .doit = ovs_ct_limit_cmd_del,
|
|
|
+ },
|
|
|
+ { .cmd = OVS_CT_LIMIT_CMD_GET,
|
|
|
+ .flags = 0, /* OK for unprivileged users. */
|
|
|
+ .policy = ct_limit_policy,
|
|
|
+ .doit = ovs_ct_limit_cmd_get,
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+static const struct genl_multicast_group ovs_ct_limit_multicast_group = {
|
|
|
+ .name = OVS_CT_LIMIT_MCGROUP,
|
|
|
+};
|
|
|
+
|
|
|
+struct genl_family dp_ct_limit_genl_family __ro_after_init = {
|
|
|
+ .hdrsize = sizeof(struct ovs_header),
|
|
|
+ .name = OVS_CT_LIMIT_FAMILY,
|
|
|
+ .version = OVS_CT_LIMIT_VERSION,
|
|
|
+ .maxattr = OVS_CT_LIMIT_ATTR_MAX,
|
|
|
+ .netnsok = true,
|
|
|
+ .parallel_ops = true,
|
|
|
+ .ops = ct_limit_genl_ops,
|
|
|
+ .n_ops = ARRAY_SIZE(ct_limit_genl_ops),
|
|
|
+ .mcgrps = &ovs_ct_limit_multicast_group,
|
|
|
+ .n_mcgrps = 1,
|
|
|
+ .module = THIS_MODULE,
|
|
|
+};
|
|
|
+#endif
|
|
|
+
|
|
|
+int ovs_ct_init(struct net *net)
|
|
|
{
|
|
|
unsigned int n_bits = sizeof(struct ovs_key_ct_labels) * BITS_PER_BYTE;
|
|
|
struct ovs_net *ovs_net = net_generic(net, ovs_net_id);
|
|
@@ -1666,12 +2205,22 @@ void ovs_ct_init(struct net *net)
|
|
|
} else {
|
|
|
ovs_net->xt_label = true;
|
|
|
}
|
|
|
+
|
|
|
+#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT)
|
|
|
+ return ovs_ct_limit_init(net, ovs_net);
|
|
|
+#else
|
|
|
+ return 0;
|
|
|
+#endif
|
|
|
}
|
|
|
|
|
|
void ovs_ct_exit(struct net *net)
|
|
|
{
|
|
|
struct ovs_net *ovs_net = net_generic(net, ovs_net_id);
|
|
|
|
|
|
+#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT)
|
|
|
+ ovs_ct_limit_exit(net, ovs_net);
|
|
|
+#endif
|
|
|
+
|
|
|
if (ovs_net->xt_label)
|
|
|
nf_connlabels_put(net);
|
|
|
}
|