summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/linux/netfilter/ipset/ip_set.h6
-rw-r--r--include/net/ip_vs.h21
-rw-r--r--net/bridge/netfilter/ebt_nflog.c17
-rw-r--r--net/netfilter/ipset/ip_set_bitmap_gen.h4
-rw-r--r--net/netfilter/ipset/ip_set_core.c52
-rw-r--r--net/netfilter/ipset/ip_set_hash_gen.h304
-rw-r--r--net/netfilter/ipset/ip_set_hash_ipportnet.c4
-rw-r--r--net/netfilter/ipset/ip_set_hash_net.c4
-rw-r--r--net/netfilter/ipset/ip_set_hash_netiface.c4
-rw-r--r--net/netfilter/ipset/ip_set_hash_netnet.c12
-rw-r--r--net/netfilter/ipset/ip_set_hash_netport.c4
-rw-r--r--net/netfilter/ipset/ip_set_hash_netportnet.c12
-rw-r--r--net/netfilter/ipset/ip_set_list_set.c4
-rw-r--r--net/netfilter/ipvs/ip_vs_core.c67
-rw-r--r--net/netfilter/ipvs/ip_vs_est.c10
-rw-r--r--net/netfilter/ipvs/ip_vs_proto_sctp.c2
-rw-r--r--net/netfilter/ipvs/ip_vs_xmit.c2
17 files changed, 331 insertions, 198 deletions
diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
index b98331572ad2..c46864cc6623 100644
--- a/include/linux/netfilter/ipset/ip_set.h
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -244,8 +244,8 @@ extern void ip_set_type_unregister(struct ip_set_type *set_type);
/* A generic IP set */
struct ip_set {
- /* For call_cru in destroy */
- struct rcu_head rcu;
+ /* for set destruction */
+ struct rcu_work rwork;
/* The name of the set */
char name[IPSET_MAXNAMELEN];
/* Lock protecting the set data */
@@ -273,7 +273,7 @@ struct ip_set {
/* Number of elements (vs timeout) */
u32 elements;
/* Size of the dynamic extensions (vs timeout) */
- size_t ext_size;
+ atomic64_t ext_size;
/* Element data size */
size_t dsize;
/* Offsets to extensions in elements */
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index e6ca930a3507..d2813eb795be 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -25,9 +25,7 @@
#include <linux/netfilter.h> /* for union nf_inet_addr */
#include <linux/ip.h>
#include <linux/ipv6.h> /* for struct ipv6hdr */
-#include <net/route.h>
#include <net/ipv6.h>
-#include <net/ip6_fib.h>
#if IS_ENABLED(CONFIG_NF_CONNTRACK)
#include <net/netfilter/nf_conntrack.h>
#endif
@@ -2062,7 +2060,7 @@ static inline bool ip_vs_conn_use_hash2(struct ip_vs_conn *cp)
void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
struct ip_vs_conn *cp, int dir, unsigned int toff,
- bool has_ports);
+ bool has_ports, struct ip_vs_iphdr *ciph);
#ifdef CONFIG_IP_VS_IPV6
void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
@@ -2095,30 +2093,23 @@ static inline __wsum ip_vs_check_diff2(__be16 old, __be16 new, __wsum oldsum)
return csum_partial(diff, sizeof(diff), oldsum);
}
-static inline bool ip_vs_checksum_needed(struct sk_buff *skb, int af)
+static inline bool ip_vs_checksum_needed(struct sk_buff *skb)
{
/* Checksum unnecessary or already validated? */
if (skb_csum_unnecessary(skb))
return false;
- /* LOCAL_OUT ? */
- if (!skb->dev || skb->dev->flags & IFF_LOOPBACK)
+ /* Locally generated ? */
+ if (!skb->dev)
return false;
- /* !LOCAL_IN (FORWARD) ? */
- if (af == AF_INET6) {
- if (!(dst_rt6_info(skb_dst(skb))->rt6i_flags & RTF_LOCAL))
- return false;
- } else {
- if (!(skb_rtable(skb)->rt_flags & RTCF_LOCAL))
- return false;
- }
return true;
}
static inline bool ip_vs_checksum_common_check(struct sk_buff *skb,
int offset, int proto, int af)
{
- if (!ip_vs_checksum_needed(skb, af))
+ if (!ip_vs_checksum_needed(skb))
return true;
+ /* Validate csum even for FORWARD */
return !nf_checksum(skb, NF_INET_LOCAL_IN, offset, proto, af);
}
diff --git a/net/bridge/netfilter/ebt_nflog.c b/net/bridge/netfilter/ebt_nflog.c
index 61bf8f4465ab..426f8adc912c 100644
--- a/net/bridge/netfilter/ebt_nflog.c
+++ b/net/bridge/netfilter/ebt_nflog.c
@@ -41,11 +41,25 @@ ebt_nflog_tg(struct sk_buff *skb, const struct xt_action_param *par)
static int ebt_nflog_tg_check(const struct xt_tgchk_param *par)
{
struct ebt_nflog_info *info = par->targinfo;
+ int ret;
if (info->flags & ~EBT_NFLOG_MASK)
return -EINVAL;
info->prefix[EBT_NFLOG_PREFIX_SIZE - 1] = '\0';
- return 0;
+
+ ret = nf_logger_find_get(par->family, NF_LOG_TYPE_ULOG);
+ if (ret != 0 && !par->nft_compat) {
+ request_module("%s", "nfnetlink_log");
+
+ ret = nf_logger_find_get(par->family, NF_LOG_TYPE_ULOG);
+ }
+
+ return ret;
+}
+
+static void ebt_nflog_tg_destroy(const struct xt_tgdtor_param *par)
+{
+ nf_logger_put(par->family, NF_LOG_TYPE_ULOG);
}
static struct xt_target ebt_nflog_tg_reg __read_mostly = {
@@ -54,6 +68,7 @@ static struct xt_target ebt_nflog_tg_reg __read_mostly = {
.family = NFPROTO_BRIDGE,
.target = ebt_nflog_tg,
.checkentry = ebt_nflog_tg_check,
+ .destroy = ebt_nflog_tg_destroy,
.targetsize = sizeof(struct ebt_nflog_info),
.me = THIS_MODULE,
};
diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h
index bb9b5bed10e1..226fdf17b683 100644
--- a/net/netfilter/ipset/ip_set_bitmap_gen.h
+++ b/net/netfilter/ipset/ip_set_bitmap_gen.h
@@ -77,7 +77,7 @@ mtype_flush(struct ip_set *set)
mtype_ext_cleanup(set);
bitmap_zero(map->members, map->elements);
set->elements = 0;
- set->ext_size = 0;
+ atomic64_set(&set->ext_size, 0);
}
/* Calculate the actual memory size of the set data */
@@ -93,7 +93,7 @@ mtype_head(struct ip_set *set, struct sk_buff *skb)
{
const struct mtype *map = set->data;
struct nlattr *nested;
- size_t memsize = mtype_memsize(map, set->dsize) + set->ext_size;
+ size_t memsize = mtype_memsize(map, set->dsize) + atomic64_read(&set->ext_size);
nested = nla_nest_start(skb, IPSET_ATTR_DATA);
if (!nested)
diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index 6cfad152d7d1..543851a923d0 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -25,6 +25,7 @@
static LIST_HEAD(ip_set_type_list); /* all registered set types */
static DEFINE_MUTEX(ip_set_type_mutex); /* protects ip_set_type_list */
static DEFINE_RWLOCK(ip_set_ref_lock); /* protects the set refs */
+static struct workqueue_struct *ipset_destroy_wq;
struct ip_set_net {
struct ip_set * __rcu *ip_set_list; /* all individual sets */
@@ -350,7 +351,7 @@ ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment,
size_t len = ext->comment ? strlen(ext->comment) : 0;
if (unlikely(c)) {
- set->ext_size -= sizeof(*c) + strlen(c->str) + 1;
+ atomic64_sub(sizeof(*c) + strlen(c->str) + 1, &set->ext_size);
rcu_assign_pointer(comment->c, NULL);
kfree_rcu(c, rcu);
}
@@ -362,7 +363,7 @@ ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment,
if (unlikely(!c))
return;
strscpy(c->str, ext->comment, len + 1);
- set->ext_size += sizeof(*c) + strlen(c->str) + 1;
+ atomic64_add(sizeof(*c) + strlen(c->str) + 1, &set->ext_size);
rcu_assign_pointer(comment->c, c);
}
EXPORT_SYMBOL_GPL(ip_set_init_comment);
@@ -392,7 +393,7 @@ ip_set_comment_free(struct ip_set *set, void *ptr)
c = rcu_dereference_protected(comment->c, 1);
if (unlikely(!c))
return;
- set->ext_size -= sizeof(*c) + strlen(c->str) + 1;
+ atomic64_sub(sizeof(*c) + strlen(c->str) + 1, &set->ext_size);
rcu_assign_pointer(comment->c, NULL);
kfree_rcu(c, rcu);
}
@@ -1178,22 +1179,26 @@ ip_set_setname_policy[IPSET_ATTR_CMD_MAX + 1] = {
.len = IPSET_MAXNAMELEN - 1 },
};
-/* In order to return quickly when destroying a single set, it is split
- * into two stages:
- * - Cancel garbage collector
- * - Destroy the set itself via call_rcu()
- */
-
static void
-ip_set_destroy_set_rcu(struct rcu_head *head)
+destroy_and_free_set(struct ip_set *set)
{
- struct ip_set *set = container_of(head, struct ip_set, rcu);
-
set->variant->destroy(set);
module_put(set->type->me);
kfree(set);
}
+/* In order to return quickly when destroying a single set,
+ * destruction is done asynchronously via work queues.
+ */
+static void
+ip_set_destroy_set_work(struct work_struct *work)
+{
+ struct ip_set *set = container_of(to_rcu_work(work),
+ struct ip_set, rwork);
+
+ destroy_and_free_set(set);
+}
+
static void
_destroy_all_sets(struct ip_set_net *inst)
{
@@ -1283,7 +1288,8 @@ static int ip_set_destroy(struct sk_buff *skb, const struct nfnl_info *info,
/* Must wait for flush to be really finished */
rcu_barrier();
}
- call_rcu(&s->rcu, ip_set_destroy_set_rcu);
+ INIT_RCU_WORK(&s->rwork, ip_set_destroy_set_work);
+ queue_rcu_work(ipset_destroy_wq, &s->rwork);
}
return 0;
out:
@@ -2421,18 +2427,23 @@ static struct pernet_operations ip_set_net_ops = {
static int __init
ip_set_init(void)
{
- int ret = register_pernet_subsys(&ip_set_net_ops);
+ int ret;
+
+ ipset_destroy_wq = alloc_ordered_workqueue("ipset_destroy_wq", 0);
+ if (!ipset_destroy_wq)
+ return -ENOMEM;
+ ret = register_pernet_subsys(&ip_set_net_ops);
if (ret) {
pr_err("ip_set: cannot register pernet_subsys.\n");
- return ret;
+ goto out_wq;
}
ret = nfnetlink_subsys_register(&ip_set_netlink_subsys);
if (ret != 0) {
pr_err("ip_set: cannot register with nfnetlink.\n");
unregister_pernet_subsys(&ip_set_net_ops);
- return ret;
+ goto out_wq;
}
ret = nf_register_sockopt(&so_set);
@@ -2440,10 +2451,13 @@ ip_set_init(void)
pr_err("SO_SET registry failed: %d\n", ret);
nfnetlink_subsys_unregister(&ip_set_netlink_subsys);
unregister_pernet_subsys(&ip_set_net_ops);
- return ret;
+ goto out_wq;
}
return 0;
+out_wq:
+ destroy_workqueue(ipset_destroy_wq);
+ return ret;
}
static void __exit
@@ -2453,9 +2467,7 @@ ip_set_fini(void)
nfnetlink_subsys_unregister(&ip_set_netlink_subsys);
unregister_pernet_subsys(&ip_set_net_ops);
- /* Wait for call_rcu() in destroy */
- rcu_barrier();
-
+ destroy_workqueue(ipset_destroy_wq);
pr_debug("these are the famous last words\n");
}
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index b2d77973272d..f00c82acd7f0 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -99,9 +99,15 @@ struct htable {
#endif
/* Book-keeping of the prefixes added to the set */
+struct net_prefix {
+ u8 cidr; /* the cidr value */
+ u32 count; /* number of elements of this cidr */
+};
+
struct net_prefixes {
- u32 nets[IPSET_NET_COUNT]; /* number of elements for this cidr */
- u8 cidr[IPSET_NET_COUNT]; /* the cidr value */
+ struct rcu_head rcu;
+ u8 len;
+ struct net_prefix nets[] __counted_by(len);
};
/* Compute the hash table size */
@@ -127,11 +133,6 @@ htable_size(u8 hbits)
#else
#define __CIDR(cidr, i) (cidr)
#endif
-
-/* cidr + 1 is stored in net_prefixes to support /0 */
-#define NCIDR_PUT(cidr) ((cidr) + 1)
-#define NCIDR_GET(cidr) ((cidr) - 1)
-
#ifdef IP_SET_HASH_WITH_NETS_PACKED
/* When cidr is packed with nomatch, cidr - 1 is stored in the data entry */
#define DCIDR_PUT(cidr) ((cidr) - 1)
@@ -141,21 +142,11 @@ htable_size(u8 hbits)
#define DCIDR_GET(cidr, i) __CIDR(cidr, i)
#endif
-#define INIT_CIDR(cidr, host_mask) \
- DCIDR_PUT(((cidr) ? NCIDR_GET(cidr) : host_mask))
-
-#ifdef IP_SET_HASH_WITH_NET0
-/* cidr from 0 to HOST_MASK value and c = cidr + 1 */
-#define NLEN (HOST_MASK + 1)
-#define CIDR_POS(c) ((c) - 1)
-#else
-/* cidr from 1 to HOST_MASK value and c = cidr + 1 */
-#define NLEN HOST_MASK
-#define CIDR_POS(c) ((c) - 2)
-#endif
+#define INIT_CIDR(n, host_mask) ({ \
+ const struct net_prefixes *__n = rcu_dereference(n); \
+ DCIDR_PUT((__n)->len ? (__n)->nets[0].cidr : host_mask);\
+})
-#else
-#define NLEN 0
#endif /* IP_SET_HASH_WITH_NETS */
#define SET_ELEM_EXPIRED(set, d) \
@@ -204,12 +195,15 @@ static const union nf_inet_addr zeromask = {};
#undef mtype_ext_cleanup
#undef mtype_add_cidr
#undef mtype_del_cidr
+#undef mtype_del_cidr_all
#undef mtype_ahash_memsize
#undef mtype_flush
#undef mtype_destroy
#undef mtype_same_set
#undef mtype_kadt
#undef mtype_uadt
+#undef mtype_bucket_size
+#undef mtype_hash_size
#undef mtype_add
#undef mtype_del
@@ -249,12 +243,15 @@ static const union nf_inet_addr zeromask = {};
#define mtype_ext_cleanup IPSET_TOKEN(MTYPE, _ext_cleanup)
#define mtype_add_cidr IPSET_TOKEN(MTYPE, _add_cidr)
#define mtype_del_cidr IPSET_TOKEN(MTYPE, _del_cidr)
+#define mtype_del_cidr_all IPSET_TOKEN(MTYPE, _del_cidr_all)
#define mtype_ahash_memsize IPSET_TOKEN(MTYPE, _ahash_memsize)
#define mtype_flush IPSET_TOKEN(MTYPE, _flush)
#define mtype_destroy IPSET_TOKEN(MTYPE, _destroy)
#define mtype_same_set IPSET_TOKEN(MTYPE, _same_set)
#define mtype_kadt IPSET_TOKEN(MTYPE, _kadt)
#define mtype_uadt IPSET_TOKEN(MTYPE, _uadt)
+#define mtype_bucket_size IPSET_TOKEN(MTYPE, _bucket_size)
+#define mtype_hash_size IPSET_TOKEN(MTYPE, _hash_size)
#define mtype_add IPSET_TOKEN(MTYPE, _add)
#define mtype_del IPSET_TOKEN(MTYPE, _del)
@@ -292,6 +289,7 @@ static const union nf_inet_addr zeromask = {};
/* The generic hash structure */
struct htype {
struct htable __rcu *table; /* the hash table */
+ struct net_prefixes __rcu *rnets[IPSET_NET_COUNT]; /* cidr prefixes */
struct htable_gc gc; /* gc workqueue */
u32 maxelem; /* max elements in the hash */
u32 initval; /* random jhash init value */
@@ -303,9 +301,6 @@ struct htype {
u8 netmask; /* netmask value for subnets to store */
union nf_inet_addr bitmask; /* stores bitmask */
#endif
-#ifdef IP_SET_HASH_WITH_NETS
- struct net_prefixes nets[NLEN]; /* book-keeping of prefixes */
-#endif
/* Because 'next' is IPv4/IPv6 dependent, no elements of this
* structure and referred in create() may come after 'next'.
*/
@@ -326,55 +321,108 @@ struct mtype_resize_ad {
/* Network cidr size book keeping when the hash stores different
* sized networks. cidr == real cidr + 1 to support /0.
*/
-static void
+static int
mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
{
- int i, j;
+ struct net_prefixes *nets, *tmp;
+ int i, j, found, len = 0, ret = 0;
spin_lock_bh(&set->lock);
+ nets = __ipset_dereference(h->rnets[n]);
/* Add in increasing prefix order, so larger cidr first */
- for (i = 0, j = -1; i < NLEN && h->nets[i].cidr[n]; i++) {
- if (j != -1) {
+ for (i = 0, found = -1; i < nets->len; i++) {
+ if (nets->nets[i].count)
+ len++;
+ if (found != -1) {
continue;
- } else if (h->nets[i].cidr[n] < cidr) {
- j = i;
- } else if (h->nets[i].cidr[n] == cidr) {
- h->nets[CIDR_POS(cidr)].nets[n]++;
+ } else if (nets->nets[i].cidr < cidr) {
+ found = i;
+ } else if (nets->nets[i].cidr == cidr) {
+ nets->nets[i].count++;
goto unlock;
}
}
- if (j != -1) {
- for (; i > j; i--)
- h->nets[i].cidr[n] = h->nets[i - 1].cidr[n];
+ len++;
+ tmp = kzalloc_flex(*tmp, nets, len, GFP_ATOMIC);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ tmp->len = len;
+ for (i = 0, j = 0; i < nets->len; i++) {
+ if (i == found) {
+ tmp->nets[j].cidr = cidr;
+ tmp->nets[j++].count = 1;
+ }
+ if (!nets->nets[i].count)
+ continue;
+ tmp->nets[j].cidr = nets->nets[i].cidr;
+ tmp->nets[j++].count = nets->nets[i].count;
}
- h->nets[i].cidr[n] = cidr;
- h->nets[CIDR_POS(cidr)].nets[n] = 1;
+ if (found == -1) {
+ tmp->nets[j].cidr = cidr;
+ tmp->nets[j].count = 1;
+ }
+ rcu_assign_pointer(h->rnets[n], tmp);
+ kfree_rcu(nets, rcu);
unlock:
spin_unlock_bh(&set->lock);
+ return ret;
}
static void
mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
{
- u8 i, j, net_end = NLEN - 1;
+ struct net_prefixes *nets, *tmp;
+ u8 i, j, len = 0;
+ int found;
spin_lock_bh(&set->lock);
- for (i = 0; i < NLEN; i++) {
- if (h->nets[i].cidr[n] != cidr)
- continue;
- h->nets[CIDR_POS(cidr)].nets[n]--;
- if (h->nets[CIDR_POS(cidr)].nets[n] > 0)
- goto unlock;
- for (j = i; j < net_end && h->nets[j].cidr[n]; j++)
- h->nets[j].cidr[n] = h->nets[j + 1].cidr[n];
- h->nets[j].cidr[n] = 0;
+ nets = __ipset_dereference(h->rnets[n]);
+ for (i = 0, found = -1; i < nets->len; i++) {
+ if (nets->nets[i].count)
+ len++;
+ if (nets->nets[i].cidr == cidr)
+ found = i;
+ }
+ if (unlikely(found == -1))
+ goto unlock;
+
+ nets->nets[found].count--;
+ if (nets->nets[found].count)
+ goto unlock;
+ len--;
+ tmp = kzalloc_flex(*tmp, nets, len, GFP_ATOMIC);
+ if (!tmp)
+ /* Leave a hole */
goto unlock;
+
+ tmp->len = len;
+ for (i = 0, j = 0; i < nets->len; i++) {
+ if (!nets->nets[i].count || i == found)
+ continue;
+ tmp->nets[j].cidr = nets->nets[i].cidr;
+ tmp->nets[j++].count = nets->nets[i].count;
}
+ rcu_assign_pointer(h->rnets[n], tmp);
+ kfree_rcu(nets, rcu);
unlock:
spin_unlock_bh(&set->lock);
}
#endif
+static void
+mtype_del_cidr_all(struct ip_set *set, struct htype *h, const struct mtype_elem *data)
+{
+#ifdef IP_SET_HASH_WITH_NETS
+ int k;
+
+ for (k = 0; k < IPSET_NET_COUNT; k++)
+ mtype_del_cidr(set, h, DCIDR_GET(data->cidr, k), k);
+#endif
+}
+
/* Calculate the actual memory size of the set data */
static size_t
mtype_ahash_memsize(const struct htype *h, const struct htable *t)
@@ -402,6 +450,9 @@ static void
mtype_flush(struct ip_set *set)
{
struct htype *h = set->data;
+#ifdef IP_SET_HASH_WITH_NETS
+ struct net_prefixes *nets, *tmp;
+#endif
struct htable *t;
struct hbucket *n;
u32 r, i;
@@ -425,7 +476,19 @@ mtype_flush(struct ip_set *set)
spin_unlock_bh(&t->hregion[r].lock);
}
#ifdef IP_SET_HASH_WITH_NETS
- memset(h->nets, 0, sizeof(h->nets));
+ for (i = 0; i < IPSET_NET_COUNT; i++) {
+ nets = ipset_dereference_nfnl(h->rnets[i]);
+ tmp = kzalloc_obj(*tmp, GFP_ATOMIC);
+ if (!tmp) {
+ u8 j;
+
+ for (j = 0; j < nets->len; j++)
+ nets->nets[j].count = 0;
+ } else {
+ rcu_assign_pointer(h->rnets[i], tmp);
+ kfree_rcu(nets, rcu);
+ }
+ }
#endif
}
@@ -433,6 +496,9 @@ mtype_flush(struct ip_set *set)
static void
mtype_ahash_destroy(struct ip_set *set, struct htable *t, bool ext_destroy)
{
+#ifdef IP_SET_HASH_WITH_NETS
+ struct htype *h = set->data;
+#endif
struct hbucket *n;
u32 i;
@@ -446,6 +512,11 @@ mtype_ahash_destroy(struct ip_set *set, struct htable *t, bool ext_destroy)
kfree(n);
}
+#ifdef IP_SET_HASH_WITH_NETS
+ if (ext_destroy)
+ for (i = 0; i < IPSET_NET_COUNT; i++)
+ kfree(rcu_dereference_raw(h->rnets[i]));
+#endif
ip_set_free(t->hregion);
ip_set_free(t);
}
@@ -493,9 +564,6 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r)
struct mtype_elem *data;
u32 i, j, d;
size_t dsize = set->dsize;
-#ifdef IP_SET_HASH_WITH_NETS
- u8 k;
-#endif
u8 pos, htable_bits = t->htable_bits;
spin_lock_bh(&t->hregion[r].lock);
@@ -516,12 +584,7 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r)
pr_debug("expired %u/%u\n", i, j);
clear_bit(j, n->used);
smp_mb__after_atomic();
-#ifdef IP_SET_HASH_WITH_NETS
- for (k = 0; k < IPSET_NET_COUNT; k++)
- mtype_del_cidr(set, h,
- NCIDR_PUT(DCIDR_GET(data->cidr, k)),
- k);
-#endif
+ mtype_del_cidr_all(set, h, data);
t->hregion[r].elements--;
ip_set_ext_destroy(set, data);
d++;
@@ -947,12 +1010,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
j = 0;
data = ahash_data(n, j, set->dsize);
if (!deleted) {
-#ifdef IP_SET_HASH_WITH_NETS
- for (i = 0; i < IPSET_NET_COUNT; i++)
- mtype_del_cidr(set, h,
- NCIDR_PUT(DCIDR_GET(data->cidr, i)),
- i);
-#endif
+ mtype_del_cidr_all(set, h, data);
ip_set_ext_destroy(set, data);
t->hregion[r].elements--;
}
@@ -996,7 +1054,7 @@ copy_data:
t->hregion[r].elements++;
#ifdef IP_SET_HASH_WITH_NETS
for (i = 0; i < IPSET_NET_COUNT; i++)
- mtype_add_cidr(set, h, NCIDR_PUT(DCIDR_GET(d->cidr, i)), i);
+ mtype_add_cidr(set, h, DCIDR_GET(d->cidr, i), i);
#endif
memcpy(data, d, sizeof(struct mtype_elem));
overwrite_extensions:
@@ -1107,11 +1165,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
if (i + 1 == pos)
smp_store_release(&n->pos, --pos);
t->hregion[r].elements--;
-#ifdef IP_SET_HASH_WITH_NETS
- for (j = 0; j < IPSET_NET_COUNT; j++)
- mtype_del_cidr(set, h,
- NCIDR_PUT(DCIDR_GET(d->cidr, j)), j);
-#endif
+ mtype_del_cidr_all(set, h, d);
ip_set_ext_destroy(set, data);
if (t->resizing && ext && ext->target) {
@@ -1193,28 +1247,37 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
{
struct htype *h = set->data;
struct htable *t = rcu_dereference_bh(h->table);
+ struct net_prefixes *nets0;
struct hbucket *n;
struct mtype_elem *data;
#if IPSET_NET_COUNT == 2
+ struct net_prefixes *nets1;
struct mtype_elem orig = *d;
- int ret, i, j = 0, k;
+ int ret, i, j, k;
#else
- int ret, i, j = 0;
+ int ret, i, j;
#endif
u32 key, multi = 0;
u8 pos;
pr_debug("test by nets\n");
- for (; j < NLEN && h->nets[j].cidr[0] && !multi; j++) {
+ rcu_read_lock_bh();
+ nets0 = rcu_dereference_bh(h->rnets[0]);
+#if IPSET_NET_COUNT == 2
+ nets1 = rcu_dereference_bh(h->rnets[1]);
+#endif
+ for (j = 0; j < nets0->len && !multi; j++) {
+ if (!nets0->nets[j].count)
+ continue;
#if IPSET_NET_COUNT == 2
mtype_data_reset_elem(d, &orig);
- mtype_data_netmask(d, NCIDR_GET(h->nets[j].cidr[0]), false);
- for (k = 0; k < NLEN && h->nets[k].cidr[1] && !multi;
- k++) {
- mtype_data_netmask(d, NCIDR_GET(h->nets[k].cidr[1]),
- true);
+ mtype_data_netmask(d, nets0->nets[j].cidr, false);
+ for (k = 0; k < nets1->len && !multi; k++) {
+ if (!nets1->nets[k].count)
+ continue;
+ mtype_data_netmask(d, nets1->nets[k].cidr, true);
#else
- mtype_data_netmask(d, NCIDR_GET(h->nets[j].cidr[0]));
+ mtype_data_netmask(d, nets0->nets[j].cidr);
#endif
key = HKEY(d, h->initval, t->htable_bits);
n = rcu_dereference_bh(hbucket(t, key));
@@ -1229,7 +1292,7 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
continue;
ret = mtype_data_match(data, ext, mext, set, flags);
if (ret != 0)
- return ret;
+ goto unlock;
#ifdef IP_SET_HASH_WITH_MULTI
/* No match, reset multiple match flag */
multi = 0;
@@ -1239,7 +1302,10 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
}
#endif
}
- return 0;
+ ret = 0;
+unlock:
+ rcu_read_unlock_bh();
+ return ret;
}
#endif
@@ -1294,6 +1360,24 @@ out:
return ret;
}
+static u32 mtype_hash_size(const struct htype *h)
+{
+ const struct htable *t;
+ u8 htable_bits;
+
+ rcu_read_lock();
+ t = rcu_dereference(h->table);
+ htable_bits = t->htable_bits;
+ rcu_read_unlock();
+
+ return jhash_size(htable_bits);
+}
+
+static u32 mtype_bucket_size(const struct htype *h)
+{
+ return h->bucketsize;
+}
+
/* Reply a HEADER request: fill out the header part of the set */
static int
mtype_head(struct ip_set *set, struct sk_buff *skb)
@@ -1304,21 +1388,20 @@ mtype_head(struct ip_set *set, struct sk_buff *skb)
size_t memsize;
u32 elements = 0;
size_t ext_size = 0;
- u8 htable_bits;
rcu_read_lock_bh();
t = rcu_dereference_bh(h->table);
mtype_ext_size(set, &elements, &ext_size);
- memsize = mtype_ahash_memsize(h, t) + ext_size + set->ext_size;
- htable_bits = t->htable_bits;
+ memsize = mtype_ahash_memsize(h, t) + ext_size + atomic64_read(&set->ext_size);
rcu_read_unlock_bh();
nested = nla_nest_start(skb, IPSET_ATTR_DATA);
if (!nested)
goto nla_put_failure;
- if (nla_put_net32(skb, IPSET_ATTR_HASHSIZE,
- htonl(jhash_size(htable_bits))) ||
- nla_put_net32(skb, IPSET_ATTR_MAXELEM, htonl(h->maxelem)))
+
+ if (nla_put_net32(skb, IPSET_ATTR_HASHSIZE, htonl(mtype_hash_size(h))))
+ goto nla_put_failure;
+ if (nla_put_net32(skb, IPSET_ATTR_MAXELEM, htonl(h->maxelem)))
goto nla_put_failure;
#ifdef IP_SET_HASH_WITH_BITMASK
/* if netmask is set to anything other than HOST_MASK we know that the user supplied netmask
@@ -1342,8 +1425,9 @@ mtype_head(struct ip_set *set, struct sk_buff *skb)
goto nla_put_failure;
#endif
if (set->flags & IPSET_CREATE_FLAG_BUCKETSIZE) {
- if (nla_put_u8(skb, IPSET_ATTR_BUCKETSIZE, h->bucketsize) ||
- nla_put_net32(skb, IPSET_ATTR_INITVAL, htonl(h->initval)))
+ if (nla_put_u8(skb, IPSET_ATTR_BUCKETSIZE, mtype_bucket_size(h)))
+ goto nla_put_failure;
+ if (nla_put_net32(skb, IPSET_ATTR_INITVAL, htonl(h->initval)))
goto nla_put_failure;
}
if (nla_put_net32(skb, IPSET_ATTR_REFERENCES, htonl(set->ref)) ||
@@ -1505,6 +1589,9 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
u8 netmask = set->family == NFPROTO_IPV4 ? 32 : 128;
union nf_inet_addr bitmask = onesmask;
#endif
+#ifdef IP_SET_HASH_WITH_NETS
+ struct net_prefixes *nets;
+#endif
size_t hsize;
struct htype *h;
struct htable *t;
@@ -1604,21 +1691,25 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
*/
hbits = fls(hashsize - 1);
hsize = htable_size(hbits);
- if (hsize == 0) {
- kfree(h);
- return -ENOMEM;
- }
+ if (hsize == 0)
+ goto free_h;
t = ip_set_alloc(hsize);
- if (!t) {
- kfree(h);
- return -ENOMEM;
- }
+ if (!t)
+ goto free_h;
t->hregion = ip_set_alloc(ahash_sizeof_regions(hbits));
- if (!t->hregion) {
- ip_set_free(t);
- kfree(h);
- return -ENOMEM;
+ if (!t->hregion)
+ goto free_t;
+#ifdef IP_SET_HASH_WITH_NETS
+ for (i = 0; i < IPSET_NET_COUNT; i++) {
+ nets = kzalloc_obj(*nets);
+ if (!nets) {
+ while (i > 0)
+ kfree(rcu_dereference_raw(h->rnets[--i]));
+ goto free_hregion;
+ }
+ RCU_INIT_POINTER(h->rnets[i], nets);
}
+#endif
h->gc.set = set;
spin_lock_init(&h->gc.lock);
for (i = 0; i < ahash_numof_locks(hbits); i++)
@@ -1650,6 +1741,7 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
INIT_LIST_HEAD(&t->ad);
RCU_INIT_POINTER(h->table, t);
set->data = h;
+
#ifndef IP_SET_PROTO_UNDEF
if (set->family == NFPROTO_IPV4) {
#endif
@@ -1678,10 +1770,20 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
#endif
}
pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)\n",
- set->name, jhash_size(t->htable_bits),
+ set->name, mtype_hash_size(h),
t->htable_bits, h->maxelem, set->data, t);
return 0;
+
+#ifdef IP_SET_HASH_WITH_NETS
+free_hregion:
+ ip_set_free(t->hregion);
+#endif
+free_t:
+ ip_set_free(t);
+free_h:
+ kfree(h);
+ return -ENOMEM;
}
#endif /* IP_SET_EMIT_CREATE */
diff --git a/net/netfilter/ipset/ip_set_hash_ipportnet.c b/net/netfilter/ipset/ip_set_hash_ipportnet.c
index 2d6652d43199..195853a25b06 100644
--- a/net/netfilter/ipset/ip_set_hash_ipportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_ipportnet.c
@@ -138,7 +138,7 @@ hash_ipportnet4_kadt(struct ip_set *set, const struct sk_buff *skb,
const struct hash_ipportnet4 *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct hash_ipportnet4_elem e = {
- .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+ .cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
};
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
@@ -398,7 +398,7 @@ hash_ipportnet6_kadt(struct ip_set *set, const struct sk_buff *skb,
const struct hash_ipportnet6 *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct hash_ipportnet6_elem e = {
- .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+ .cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
};
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
diff --git a/net/netfilter/ipset/ip_set_hash_net.c b/net/netfilter/ipset/ip_set_hash_net.c
index ce0a9ce5a91f..092f3c9281b8 100644
--- a/net/netfilter/ipset/ip_set_hash_net.c
+++ b/net/netfilter/ipset/ip_set_hash_net.c
@@ -117,7 +117,7 @@ hash_net4_kadt(struct ip_set *set, const struct sk_buff *skb,
const struct hash_net4 *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct hash_net4_elem e = {
- .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+ .cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
};
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
@@ -291,7 +291,7 @@ hash_net6_kadt(struct ip_set *set, const struct sk_buff *skb,
const struct hash_net6 *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct hash_net6_elem e = {
- .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+ .cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
};
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
diff --git a/net/netfilter/ipset/ip_set_hash_netiface.c b/net/netfilter/ipset/ip_set_hash_netiface.c
index 30a655e5c4fd..b44b95f766b7 100644
--- a/net/netfilter/ipset/ip_set_hash_netiface.c
+++ b/net/netfilter/ipset/ip_set_hash_netiface.c
@@ -161,7 +161,7 @@ hash_netiface4_kadt(struct ip_set *set, const struct sk_buff *skb,
struct hash_netiface4 *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct hash_netiface4_elem e = {
- .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+ .cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
.elem = 1,
};
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
@@ -382,7 +382,7 @@ hash_netiface6_kadt(struct ip_set *set, const struct sk_buff *skb,
struct hash_netiface6 *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct hash_netiface6_elem e = {
- .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+ .cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
.elem = 1,
};
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
diff --git a/net/netfilter/ipset/ip_set_hash_netnet.c b/net/netfilter/ipset/ip_set_hash_netnet.c
index 8fbe649c9dd3..f7c8a1cc30fc 100644
--- a/net/netfilter/ipset/ip_set_hash_netnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netnet.c
@@ -149,8 +149,10 @@ hash_netnet4_kadt(struct ip_set *set, const struct sk_buff *skb,
struct hash_netnet4_elem e = { };
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
- e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK);
- e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK);
+ rcu_read_lock_bh();
+ e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
+ e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
+ rcu_read_unlock_bh();
if (adt == IPSET_TEST)
e.ccmp = (HOST_MASK << (sizeof(e.cidr[0]) * 8)) | HOST_MASK;
@@ -388,8 +390,10 @@ hash_netnet6_kadt(struct ip_set *set, const struct sk_buff *skb,
struct hash_netnet6_elem e = { };
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
- e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK);
- e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK);
+ rcu_read_lock_bh();
+ e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
+ e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
+ rcu_read_unlock_bh();
if (adt == IPSET_TEST)
e.ccmp = (HOST_MASK << (sizeof(u8) * 8)) | HOST_MASK;
diff --git a/net/netfilter/ipset/ip_set_hash_netport.c b/net/netfilter/ipset/ip_set_hash_netport.c
index d1a0628df4ef..5de4b511de76 100644
--- a/net/netfilter/ipset/ip_set_hash_netport.c
+++ b/net/netfilter/ipset/ip_set_hash_netport.c
@@ -133,7 +133,7 @@ hash_netport4_kadt(struct ip_set *set, const struct sk_buff *skb,
const struct hash_netport4 *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct hash_netport4_elem e = {
- .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+ .cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
};
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
@@ -353,7 +353,7 @@ hash_netport6_kadt(struct ip_set *set, const struct sk_buff *skb,
const struct hash_netport6 *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct hash_netport6_elem e = {
- .cidr = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK),
+ .cidr = INIT_CIDR(h->rnets[0], HOST_MASK),
};
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
diff --git a/net/netfilter/ipset/ip_set_hash_netportnet.c b/net/netfilter/ipset/ip_set_hash_netportnet.c
index bf4f91b78e1d..6291532be7a5 100644
--- a/net/netfilter/ipset/ip_set_hash_netportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netportnet.c
@@ -157,8 +157,10 @@ hash_netportnet4_kadt(struct ip_set *set, const struct sk_buff *skb,
struct hash_netportnet4_elem e = { };
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
- e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK);
- e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK);
+ rcu_read_lock_bh();
+ e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
+ e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
+ rcu_read_unlock_bh();
if (adt == IPSET_TEST)
e.ccmp = (HOST_MASK << (sizeof(e.cidr[0]) * 8)) | HOST_MASK;
@@ -452,8 +454,10 @@ hash_netportnet6_kadt(struct ip_set *set, const struct sk_buff *skb,
struct hash_netportnet6_elem e = { };
struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set);
- e.cidr[0] = INIT_CIDR(h->nets[0].cidr[0], HOST_MASK);
- e.cidr[1] = INIT_CIDR(h->nets[0].cidr[1], HOST_MASK);
+ rcu_read_lock_bh();
+ e.cidr[0] = INIT_CIDR(h->rnets[0], HOST_MASK);
+ e.cidr[1] = INIT_CIDR(h->rnets[1], HOST_MASK);
+ rcu_read_unlock_bh();
if (adt == IPSET_TEST)
e.ccmp = (HOST_MASK << (sizeof(u8) * 8)) | HOST_MASK;
diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c
index 1cef84f15e8c..ca3ef9479e83 100644
--- a/net/netfilter/ipset/ip_set_list_set.c
+++ b/net/netfilter/ipset/ip_set_list_set.c
@@ -421,7 +421,7 @@ list_set_flush(struct ip_set *set)
list_for_each_entry_safe(e, n, &map->members, list)
list_set_del(set, e);
set->elements = 0;
- set->ext_size = 0;
+ atomic64_set(&set->ext_size, 0);
}
static void
@@ -455,7 +455,7 @@ list_set_head(struct ip_set *set, struct sk_buff *skb)
{
const struct list_set *map = set->data;
struct nlattr *nested;
- size_t memsize = list_set_memsize(map, set->dsize) + set->ext_size;
+ size_t memsize = list_set_memsize(map, set->dsize) + atomic64_read(&set->ext_size);
nested = nla_nest_start(skb, IPSET_ATTR_DATA);
if (!nested)
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index 6b79e0c4d9e2..0bdaeb4ed61e 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -925,28 +925,27 @@ static int ip_vs_route_me_harder(struct netns_ipvs *ipvs, int af,
*/
void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
struct ip_vs_conn *cp, int inout, unsigned int toff,
- bool has_ports)
+ bool has_ports, struct ip_vs_iphdr *ciph)
{
struct iphdr *iph = ip_hdr(skb);
struct icmphdr *icmph = (struct icmphdr *)(skb->data + toff);
- struct iphdr *ciph = (struct iphdr *)(icmph + 1);
- unsigned int coff __maybe_unused = toff + sizeof(struct icmphdr);
+ struct iphdr *cih = (struct iphdr *)(icmph + 1);
if (inout) {
iph->saddr = cp->vaddr.ip;
ip_send_check(iph);
- ciph->daddr = cp->vaddr.ip;
- ip_send_check(ciph);
+ cih->daddr = cp->vaddr.ip;
+ ip_send_check(cih);
} else {
iph->daddr = cp->daddr.ip;
ip_send_check(iph);
- ciph->saddr = cp->daddr.ip;
- ip_send_check(ciph);
+ cih->saddr = cp->daddr.ip;
+ ip_send_check(cih);
}
/* the TCP/UDP/SCTP port */
if (has_ports) {
- __be16 *ports = (void *)ciph + ciph->ihl*4;
+ __be16 *ports = (void *)(skb->data + ciph->len);
if (inout)
ports[1] = cp->vport;
@@ -960,10 +959,10 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
skb->ip_summed = CHECKSUM_UNNECESSARY;
if (inout)
- IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff,
+ IP_VS_DBG_PKT(11, AF_INET, pp, skb, ciph->off,
"Forwarding altered outgoing ICMP");
else
- IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff,
+ IP_VS_DBG_PKT(11, AF_INET, pp, skb, ciph->off,
"Forwarding altered incoming ICMP");
}
@@ -1056,7 +1055,7 @@ static int handle_response_icmp(int af, struct sk_buff *skb,
ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, has_ports, ciph);
else
#endif
- ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports);
+ ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports, ciph);
if (ip_vs_route_me_harder(cp->ipvs, af, skb, hooknum))
goto out;
@@ -1092,7 +1091,7 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
struct ip_vs_iphdr ciph;
struct ip_vs_conn *cp;
struct ip_vs_protocol *pp;
- unsigned int offset, ihl;
+ unsigned int offset;
union nf_inet_addr snet;
*related = 1;
@@ -1105,7 +1104,6 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
return NF_ACCEPT;
}
- ihl = ipvsh->len;
offset = ipvsh->len;
ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph);
if (ic == NULL)
@@ -1131,11 +1129,15 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
/* Now find the contained IP header */
offset += sizeof(_icmph);
+ if (!ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, true, &ciph))
+ return NF_ACCEPT; /* The packet looks wrong, ignore */
+
cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
- if (!(cih && cih->version == 4 && cih->ihl >= 5))
+ if (!(cih && cih->version == 4 &&
+ ciph.len - ciph.off >= sizeof(struct iphdr)))
return NF_ACCEPT; /* The packet looks wrong, ignore */
- pp = ip_vs_proto_get(cih->protocol);
+ pp = ip_vs_proto_get(ciph.protocol);
if (!pp)
return NF_ACCEPT;
@@ -1146,8 +1148,6 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset,
"Checking outgoing ICMP for");
- ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, true, &ciph);
-
/* The embedded headers contain source and dest in reverse order */
cp = INDIRECT_CALL_1(pp->conn_out_get, ip_vs_conn_out_get_proto,
ipvs, AF_INET, skb, &ciph);
@@ -1155,8 +1155,8 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
return NF_ACCEPT;
snet.ip = ipvsh->saddr.ip;
- return handle_response_icmp(AF_INET, skb, &snet, cp, pp, &ciph, ihl,
- hooknum);
+ return handle_response_icmp(AF_INET, skb, &snet, cp, pp, &ciph,
+ ipvsh->len, hooknum);
}
#ifdef CONFIG_IP_VS_IPV6
@@ -1803,10 +1803,12 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
/* Now find the contained IP header */
offset += sizeof(_icmph);
cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
- if (!(cih && cih->version == 4 && cih->ihl >= 5))
+ if (!cih)
return NF_ACCEPT; /* The packet looks wrong, ignore */
- raddr = (union nf_inet_addr *)&cih->daddr;
hlen_ipip = cih->ihl * 4;
+ if (!(cih->version == 4 && hlen_ipip >= sizeof(struct iphdr)))
+ return NF_ACCEPT; /* The packet looks wrong, ignore */
+ raddr = (union nf_inet_addr *)&cih->daddr;
/* Special case for errors for IPIP/UDP/GRE tunnel packets */
tunnel = false;
@@ -1823,9 +1825,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
if (!dest || dest->tun_type != IP_VS_CONN_F_TUNNEL_TYPE_IPIP)
return NF_ACCEPT;
offset += hlen_ipip;
- cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
- if (!(cih && cih->version == 4 && cih->ihl >= 5))
- return NF_ACCEPT; /* The packet looks wrong, ignore */
tunnel = true;
} else if ((cih->protocol == IPPROTO_UDP || /* Can be UDP encap */
cih->protocol == IPPROTO_GRE) && /* Can be GRE encap */
@@ -1850,21 +1849,25 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
/* Skip IP and UDP/GRE tunnel headers */
offset = offset2 + ulen;
/* Now we should be at the original IP header */
- cih = skb_header_pointer(skb, offset, sizeof(_ciph),
- &_ciph);
- if (cih && cih->version == 4 && cih->ihl >= 5 &&
- iproto == IPPROTO_IPIP)
+ if (iproto == IPPROTO_IPIP)
tunnel = true;
else
return NF_ACCEPT;
}
}
- pd = ip_vs_proto_data_get(ipvs, cih->protocol);
+ if (!ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph))
+ return NF_ACCEPT;
+ pd = ip_vs_proto_data_get(ipvs, ciph.protocol);
if (!pd)
return NF_ACCEPT;
pp = pd->pp;
+ cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
+ if (!(cih && cih->version == 4 &&
+ ciph.len - ciph.off >= sizeof(struct iphdr)))
+ return NF_ACCEPT; /* The packet looks wrong, ignore */
+
/* Is the embedded protocol header present? */
if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag))
return NF_ACCEPT;
@@ -1872,9 +1875,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset,
"Checking incoming ICMP for");
- offset2 = offset;
- ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph);
-
/* The embedded headers contain source and dest in reverse order.
* For IPIP/UDP/GRE tunnel this is error for request, not for reply.
*/
@@ -1904,11 +1904,12 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
}
if (tunnel) {
- unsigned int hlen_orig = cih->ihl * 4;
+ unsigned int hlen_orig = ciph.len - ciph.off;
__be32 info = ic->un.gateway;
__u8 type = ic->type;
__u8 code = ic->code;
+ offset2 = offset;
/* Update the MTU */
if (ic->type == ICMP_DEST_UNREACH &&
ic->code == ICMP_FRAG_NEEDED) {
diff --git a/net/netfilter/ipvs/ip_vs_est.c b/net/netfilter/ipvs/ip_vs_est.c
index ab09f5182951..05a216a47b45 100644
--- a/net/netfilter/ipvs/ip_vs_est.c
+++ b/net/netfilter/ipvs/ip_vs_est.c
@@ -191,8 +191,11 @@ static int ip_vs_estimation_kthread(void *data)
}
/* kthread 0 will handle the calc phase */
- if (ipvs->est_calc_phase)
+ if (ipvs->est_calc_phase) {
ip_vs_est_calc_phase(ipvs);
+ if (kthread_should_stop() || !READ_ONCE(ipvs->enable))
+ return 0;
+ }
}
while (1) {
@@ -270,6 +273,7 @@ int ip_vs_est_kthread_start(struct netns_ipvs *ipvs,
kd->task = NULL;
goto out;
}
+ get_task_struct(kd->task);
set_user_nice(kd->task, sysctl_est_nice(ipvs));
if (sysctl_est_preferred_cpulist(ipvs))
@@ -286,7 +290,7 @@ void ip_vs_est_kthread_stop(struct ip_vs_est_kt_data *kd)
{
if (kd->task) {
pr_info("stopping estimator thread %d...\n", kd->id);
- kthread_stop(kd->task);
+ kthread_stop_put(kd->task);
kd->task = NULL;
}
}
@@ -526,7 +530,7 @@ static void ip_vs_est_kthread_destroy(struct ip_vs_est_kt_data *kd)
if (kd) {
if (kd->task) {
pr_info("stop unused estimator thread %d...\n", kd->id);
- kthread_stop(kd->task);
+ kthread_stop_put(kd->task);
}
ip_vs_stats_free(kd->calc_stats);
kfree(kd);
diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c
index 3dbd3096e163..c80567c73469 100644
--- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
@@ -193,7 +193,7 @@ sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
struct sctphdr *sh;
__le32 cmp, val;
- if (!ip_vs_checksum_needed(skb, af))
+ if (!ip_vs_checksum_needed(skb))
return 1;
sh = (struct sctphdr *)(skb->data + sctphoff);
cmp = sh->checksum;
diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
index 0b0c5304993a..c4508f3f43dd 100644
--- a/net/netfilter/ipvs/ip_vs_xmit.c
+++ b/net/netfilter/ipvs/ip_vs_xmit.c
@@ -1580,7 +1580,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
if (skb_cow(skb, rt->dst.dev->hard_header_len))
goto tx_error;
- ip_vs_nat_icmp(skb, pp, cp, 0, toff, has_ports);
+ ip_vs_nat_icmp(skb, pp, cp, 0, toff, has_ports, ciph);
/* Another hack: avoid icmp_send in ip_fragment */
skb->ignore_df = 1;