mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 11:19:37 +00:00
![Florian Westphal](/assets/img/avatar_default.png)
This patch further relaxes the need to drop an skb due to a clash with an existing conntrack entry. Current clash resolution handles the case where the clash occurs between two identical entries (distinct nf_conn objects with same tuples), i.e.: Original Reply existing: 10.2.3.4:42 -> 10.8.8.8:53 10.2.3.4:42 <- 10.0.0.6:5353 clashing: 10.2.3.4:42 -> 10.8.8.8:53 10.2.3.4:42 <- 10.0.0.6:5353 ... existing handling will discard the unconfirmed clashing entry and makes skb->_nfct point to the existing one. The skb can then be processed normally just as if the clash would not have existed in the first place. For other clashes, the skb needs to be dropped. This frequently happens with DNS resolvers that send A and AAAA queries back-to-back when NAT rules are present that cause packets to get different DNAT transformations applied, for example: -m statistics --mode random ... -j DNAT --dnat-to 10.0.0.6:5353 -m statistics --mode random ... -j DNAT --dnat-to 10.0.0.7:5353 In this case the A or AAAA query is dropped which incurs a costly delay during name resolution. This patch also allows this collision type: Original Reply existing: 10.2.3.4:42 -> 10.8.8.8:53 10.2.3.4:42 <- 10.0.0.6:5353 clashing: 10.2.3.4:42 -> 10.8.8.8:53 10.2.3.4:42 <- 10.0.0.7:5353 In this case, clash is in original direction -- the reply direction is still unique. The change makes it so that when the 2nd colliding packet is received, the clashing conntrack is tagged with new IPS_NAT_CLASH_BIT, gets a fixed 1 second timeout and is inserted in the reply direction only. The entry is hidden from 'conntrack -L', it will time out quickly and it can be early dropped because it will never progress to the ASSURED state. To avoid special-casing the delete code path to special case the ORIGINAL hlist_nulls node, a new helper, "hlist_nulls_add_fake", is added so hlist_nulls_del() will work. Example: CPU A: CPU B: 1. 10.2.3.4:42 -> 10.8.8.8:53 (A) 2. 10.2.3.4:42 -> 10.8.8.8:53 (AAAA) 3. Apply DNAT, reply changed to 10.0.0.6 4. 10.2.3.4:42 -> 10.8.8.8:53 (AAAA) 5. Apply DNAT, reply changed to 10.0.0.7 6. confirm/commit to conntrack table, no collisions 7. commit clashing entry Reply comes in: 10.2.3.4:42 <- 10.0.0.6:5353 (A) -> Finds a conntrack, DNAT is reversed & packet forwarded to 10.2.3.4:42 10.2.3.4:42 <- 10.0.0.7:5353 (AAAA) -> Finds a conntrack, DNAT is reversed & packet forwarded to 10.2.3.4:42 The conntrack entry is deleted from table, as it has the NAT_CLASH bit set. In case of a retransmit from ORIGINAL dir, all further packets will get the DNAT transformation to 10.0.0.6. I tried to come up with other solutions but they all have worse problems. Alternatives considered were: 1. Confirm ct entries at allocation time, not in postrouting. a. will cause uneccesarry work when the skb that creates the conntrack is dropped by ruleset. b. in case nat is applied, ct entry would need to be moved in the table, which requires another spinlock pair to be taken. c. breaks the 'unconfirmed entry is private to cpu' assumption: we would need to guard all nfct->ext allocation requests with ct->lock spinlock. 2. Make the unconfirmed list a hash table instead of a pcpu list. Shares drawback c) of the first alternative. 3. Document this is expected and force users to rearrange their ruleset (e.g. by using "-m cluster" instead of "-m statistics"). nft has the 'jhash' expression which can be used instead of 'numgen'. Major drawback: doesn't fix what I consider a bug, not very realistic and I believe its reasonable to have the existing rulesets to 'just work'. 4. Document this is expected and force users to steer problematic packets to the same CPU -- this would serialize the "allocate new conntrack entry/nat table evaluation/perform nat/confirm entry", so no race can occur. Similar drawback to 3. Another advantage of this patch compared to 1) and 2) is that there are no changes to the hot path; things are handled in the udp tracker and the clash resolution path. Cc: rcu@vger.kernel.org Cc: "Paul E. McKenney" <paulmck@kernel.org> Cc: Josh Triplett <josh@joshtriplett.org> Cc: Jozsef Kadlecsik <kadlec@netfilter.org> Signed-off-by: Florian Westphal <fw@strlen.de> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
189 lines
6.6 KiB
C
189 lines
6.6 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef _LINUX_RCULIST_NULLS_H
|
|
#define _LINUX_RCULIST_NULLS_H
|
|
|
|
#ifdef __KERNEL__
|
|
|
|
/*
|
|
* RCU-protected list version
|
|
*/
|
|
#include <linux/list_nulls.h>
|
|
#include <linux/rcupdate.h>
|
|
|
|
/**
|
|
* hlist_nulls_del_init_rcu - deletes entry from hash list with re-initialization
|
|
* @n: the element to delete from the hash list.
|
|
*
|
|
* Note: hlist_nulls_unhashed() on the node return true after this. It is
|
|
* useful for RCU based read lockfree traversal if the writer side
|
|
* must know if the list entry is still hashed or already unhashed.
|
|
*
|
|
* In particular, it means that we can not poison the forward pointers
|
|
* that may still be used for walking the hash list and we can only
|
|
* zero the pprev pointer so list_unhashed() will return true after
|
|
* this.
|
|
*
|
|
* The caller must take whatever precautions are necessary (such as
|
|
* holding appropriate locks) to avoid racing with another
|
|
* list-mutation primitive, such as hlist_nulls_add_head_rcu() or
|
|
* hlist_nulls_del_rcu(), running on this same list. However, it is
|
|
* perfectly legal to run concurrently with the _rcu list-traversal
|
|
* primitives, such as hlist_nulls_for_each_entry_rcu().
|
|
*/
|
|
static inline void hlist_nulls_del_init_rcu(struct hlist_nulls_node *n)
|
|
{
|
|
if (!hlist_nulls_unhashed(n)) {
|
|
__hlist_nulls_del(n);
|
|
WRITE_ONCE(n->pprev, NULL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* hlist_nulls_first_rcu - returns the first element of the hash list.
|
|
* @head: the head of the list.
|
|
*/
|
|
#define hlist_nulls_first_rcu(head) \
|
|
(*((struct hlist_nulls_node __rcu __force **)&(head)->first))
|
|
|
|
/**
|
|
* hlist_nulls_next_rcu - returns the element of the list after @node.
|
|
* @node: element of the list.
|
|
*/
|
|
#define hlist_nulls_next_rcu(node) \
|
|
(*((struct hlist_nulls_node __rcu __force **)&(node)->next))
|
|
|
|
/**
|
|
* hlist_nulls_del_rcu - deletes entry from hash list without re-initialization
|
|
* @n: the element to delete from the hash list.
|
|
*
|
|
* Note: hlist_nulls_unhashed() on entry does not return true after this,
|
|
* the entry is in an undefined state. It is useful for RCU based
|
|
* lockfree traversal.
|
|
*
|
|
* In particular, it means that we can not poison the forward
|
|
* pointers that may still be used for walking the hash list.
|
|
*
|
|
* The caller must take whatever precautions are necessary
|
|
* (such as holding appropriate locks) to avoid racing
|
|
* with another list-mutation primitive, such as hlist_nulls_add_head_rcu()
|
|
* or hlist_nulls_del_rcu(), running on this same list.
|
|
* However, it is perfectly legal to run concurrently with
|
|
* the _rcu list-traversal primitives, such as
|
|
* hlist_nulls_for_each_entry().
|
|
*/
|
|
static inline void hlist_nulls_del_rcu(struct hlist_nulls_node *n)
|
|
{
|
|
__hlist_nulls_del(n);
|
|
WRITE_ONCE(n->pprev, LIST_POISON2);
|
|
}
|
|
|
|
/**
|
|
* hlist_nulls_add_head_rcu
|
|
* @n: the element to add to the hash list.
|
|
* @h: the list to add to.
|
|
*
|
|
* Description:
|
|
* Adds the specified element to the specified hlist_nulls,
|
|
* while permitting racing traversals.
|
|
*
|
|
* The caller must take whatever precautions are necessary
|
|
* (such as holding appropriate locks) to avoid racing
|
|
* with another list-mutation primitive, such as hlist_nulls_add_head_rcu()
|
|
* or hlist_nulls_del_rcu(), running on this same list.
|
|
* However, it is perfectly legal to run concurrently with
|
|
* the _rcu list-traversal primitives, such as
|
|
* hlist_nulls_for_each_entry_rcu(), used to prevent memory-consistency
|
|
* problems on Alpha CPUs. Regardless of the type of CPU, the
|
|
* list-traversal primitive must be guarded by rcu_read_lock().
|
|
*/
|
|
static inline void hlist_nulls_add_head_rcu(struct hlist_nulls_node *n,
|
|
struct hlist_nulls_head *h)
|
|
{
|
|
struct hlist_nulls_node *first = h->first;
|
|
|
|
n->next = first;
|
|
WRITE_ONCE(n->pprev, &h->first);
|
|
rcu_assign_pointer(hlist_nulls_first_rcu(h), n);
|
|
if (!is_a_nulls(first))
|
|
WRITE_ONCE(first->pprev, &n->next);
|
|
}
|
|
|
|
/**
|
|
* hlist_nulls_add_tail_rcu
|
|
* @n: the element to add to the hash list.
|
|
* @h: the list to add to.
|
|
*
|
|
* Description:
|
|
* Adds the specified element to the specified hlist_nulls,
|
|
* while permitting racing traversals.
|
|
*
|
|
* The caller must take whatever precautions are necessary
|
|
* (such as holding appropriate locks) to avoid racing
|
|
* with another list-mutation primitive, such as hlist_nulls_add_head_rcu()
|
|
* or hlist_nulls_del_rcu(), running on this same list.
|
|
* However, it is perfectly legal to run concurrently with
|
|
* the _rcu list-traversal primitives, such as
|
|
* hlist_nulls_for_each_entry_rcu(), used to prevent memory-consistency
|
|
* problems on Alpha CPUs. Regardless of the type of CPU, the
|
|
* list-traversal primitive must be guarded by rcu_read_lock().
|
|
*/
|
|
static inline void hlist_nulls_add_tail_rcu(struct hlist_nulls_node *n,
|
|
struct hlist_nulls_head *h)
|
|
{
|
|
struct hlist_nulls_node *i, *last = NULL;
|
|
|
|
/* Note: write side code, so rcu accessors are not needed. */
|
|
for (i = h->first; !is_a_nulls(i); i = i->next)
|
|
last = i;
|
|
|
|
if (last) {
|
|
n->next = last->next;
|
|
n->pprev = &last->next;
|
|
rcu_assign_pointer(hlist_next_rcu(last), n);
|
|
} else {
|
|
hlist_nulls_add_head_rcu(n, h);
|
|
}
|
|
}
|
|
|
|
/* after that hlist_nulls_del will work */
|
|
static inline void hlist_nulls_add_fake(struct hlist_nulls_node *n)
|
|
{
|
|
n->pprev = &n->next;
|
|
n->next = (struct hlist_nulls_node *)NULLS_MARKER(NULL);
|
|
}
|
|
|
|
/**
|
|
* hlist_nulls_for_each_entry_rcu - iterate over rcu list of given type
|
|
* @tpos: the type * to use as a loop cursor.
|
|
* @pos: the &struct hlist_nulls_node to use as a loop cursor.
|
|
* @head: the head of the list.
|
|
* @member: the name of the hlist_nulls_node within the struct.
|
|
*
|
|
* The barrier() is needed to make sure compiler doesn't cache first element [1],
|
|
* as this loop can be restarted [2]
|
|
* [1] Documentation/core-api/atomic_ops.rst around line 114
|
|
* [2] Documentation/RCU/rculist_nulls.txt around line 146
|
|
*/
|
|
#define hlist_nulls_for_each_entry_rcu(tpos, pos, head, member) \
|
|
for (({barrier();}), \
|
|
pos = rcu_dereference_raw(hlist_nulls_first_rcu(head)); \
|
|
(!is_a_nulls(pos)) && \
|
|
({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1; }); \
|
|
pos = rcu_dereference_raw(hlist_nulls_next_rcu(pos)))
|
|
|
|
/**
|
|
* hlist_nulls_for_each_entry_safe -
|
|
* iterate over list of given type safe against removal of list entry
|
|
* @tpos: the type * to use as a loop cursor.
|
|
* @pos: the &struct hlist_nulls_node to use as a loop cursor.
|
|
* @head: the head of the list.
|
|
* @member: the name of the hlist_nulls_node within the struct.
|
|
*/
|
|
#define hlist_nulls_for_each_entry_safe(tpos, pos, head, member) \
|
|
for (({barrier();}), \
|
|
pos = rcu_dereference_raw(hlist_nulls_first_rcu(head)); \
|
|
(!is_a_nulls(pos)) && \
|
|
({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); \
|
|
pos = rcu_dereference_raw(hlist_nulls_next_rcu(pos)); 1; });)
|
|
#endif
|
|
#endif
|