Re: [iptables PATCH v2 03/14] xtables: Implement per chain rule cache

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Pablo,

On Mon, Dec 17, 2018 at 06:42:49PM +0100, Pablo Neira Ayuso wrote:
> On Thu, Dec 13, 2018 at 12:15:56PM +0100, Phil Sutter wrote:
> > Use recently introduced support for rules inside chains in libnftnl to
> > introduce a rule cache per chain instead of a global one.
> > 
> > A tricky bit is to decide if cache should be updated or not. Previously,
> > the global rule cache was populated just once and then reused unless
> > being flushed completely (via call to flush_rule_cache() with
> > NULL-pointer table argument). Resemble this behaviour by introducing a
> > boolean indicating cache status and fetch rules for all chains when
> > updating the chain cache in nft_chain_list_get().
> 
> This is a rather large rewrite: Nice because it's going to speed up
> things.
> 
> I think we should start doing ruleset generation tracking, IIRC this
> is missing in the existing codebase. Such approach requires to keep
> track of the ruleset generation number and invalidate caches.

Do you think parallel ruleset updates are a problem? None of the xtables
tools are meant to stay running for long, so I didn't expect problems
there until now. Sure, loading 10k rules with iptables-nft-restore takes
a long time, but if kernel's ruleset changes in between, I don't see how
we could recover from that (apart from restarting the whole restore).

> Let me do a bit of reviewing before we get this in, see below. Thanks!

Thanks for your review! Some comments below.

> 
> > Signed-off-by: Phil Sutter <phil@xxxxxx>
> > ---
> > Changes since v1:
> > - Update rule cache only if required.
> > ---
> >  iptables/nft.c             | 599 +++++++++++++++++--------------------
> >  iptables/nft.h             |   5 +-
> >  iptables/xtables-restore.c |   5 +-
> >  iptables/xtables-save.c    |   6 +-
> >  4 files changed, 282 insertions(+), 333 deletions(-)
> > 
> > diff --git a/iptables/nft.c b/iptables/nft.c
> > index e7a56778f8004..0869f2e222393 100644
> > --- a/iptables/nft.c
> > +++ b/iptables/nft.c
> [...]
> > @@ -815,23 +803,27 @@ static void flush_chain_cache(struct nft_handle *h, const char *tablename)
> >  		if (tablename && strcmp(h->tables[i].name, tablename))
> >  			continue;
> >  
> > -		if (h->table[i].chain_cache) {
> > -			if (tablename) {
> > -				nftnl_chain_list_foreach(h->table[i].chain_cache,
> > -							 __flush_chain_cache, NULL);
> > -				break;
> > -			} else {
> > -				nftnl_chain_list_free(h->table[i].chain_cache);
> > -				h->table[i].chain_cache = NULL;
> > -			}
> > +		if (!h->table[i].chain_cache) {
> > +			if (tablename)
> > +				return;
> > +			continue;
> >  		}
> > +		if (tablename) {
> > +			nftnl_chain_list_foreach(h->table[i].chain_cache,
> > +						 __flush_chain_cache, NULL);
> > +			return;
> > +		}
> > +
> > +		nftnl_chain_list_free(h->table[i].chain_cache);
> > +		h->table[i].chain_cache = NULL;
> >  	}
> > +	h->have_cache = false;
> 
> Maybe
> 
>         h->have_chain_cache
> 
> instead?

ACK. Simple 'have_cache' is too generic indeed.
> 
> > +	h->have_rule_cache = false;
> >  }
> >  
> >  void nft_fini(struct nft_handle *h)
> >  {
> >  	flush_chain_cache(h, NULL);
> > -	flush_rule_cache(h, NULL);
> >  	mnl_socket_close(h->nl);
> >  }
> >  
> > @@ -1191,12 +1183,15 @@ err:
> >  	return NULL;
> >  }
> >  
> > -static struct nftnl_rule_list *nft_rule_list_get(struct nft_handle *h);
> 
> Can you leave this function around?
> 
> Idea is that we call this function if we want a full cache, ie. chain + rules.
> 
> Then, we call nft_chain_list_get() to fetch chains only.
> 
> Otherwise, nft_rule_cache_get() ? Just trying this patch smaller here
> in this round for easier review.

Sure, no problem.

> > +static struct nftnl_chain *
> > +nft_chain_find(struct nft_handle *h, const char *table,
> > +	       const char *chain, bool need_rules);
> 
> We can probably use:
> 
>         nft_chain_find()
> 
> if we want a full cache, ie. give me chain and retrieve rules
> (populate cache if needed).
> 
> Otherwise, use:
> 
>         __nft_chain_find()
> 
> if we want only to fetch the chain cache, so we don't need the "bool
> need_rules". Only two spots use nft_chain_find(..., false) in this
> patch that I can see.

Will do.

> >  int
> >  nft_rule_append(struct nft_handle *h, const char *chain, const char *table,
> >  		void *data, uint64_t handle, bool verbose)
> >  {
> > +	struct nftnl_chain *c;
> >  	struct nftnl_rule *r;
> >  	int type;
> >  
> > @@ -1224,10 +1219,9 @@ nft_rule_append(struct nft_handle *h, const char *chain, const char *table,
> >  	if (verbose)
> >  		h->ops->print_rule(r, 0, FMT_PRINT_RULE);
> >  
> > -	if (!nft_rule_list_get(h))
> > -		return 0;
> > -
> > -	nftnl_rule_list_add_tail(r, h->rule_cache);
> > +	c = nft_chain_find(h, table, chain, true);
> 
> I guess we always find a match from nft_chain_find().
> 
> > +	if (c)
> 
> So this branch is probably not needed, otherwise do no ignore this
> error?
> 
> > +		nftnl_chain_rule_add(r, c);

Ah, yes. In a later patch of the same series I turn this into:

|         c = nft_chain_find(h, table, chain, true);
| -       if (c)
| -               nftnl_chain_rule_add(r, c);
| +       assert(c);
| +       nftnl_chain_rule_add_tail(r, c);

Because if the rule is not added to the chain cache, it won't end in the
batch. Also the change from rule_add_tail to rule_add is a mistake here.

[...]
> > @@ -1297,33 +1285,60 @@ err:
> >  	return MNL_CB_OK;
> >  }
> >  
> > +static int nft_rule_list_update(struct nftnl_chain *c, void *data);
> > +
> >  struct nftnl_chain_list *nft_chain_list_get(struct nft_handle *h,
> > -					    const char *table)
> > +					    const char *table, bool need_rules)
> >  {
> >  	char buf[16536];
> >  	struct nlmsghdr *nlh;
> >  	const struct builtin_table *t;
> >  	int ret;
> > +	int i;
> >  
> >  	t = nft_table_builtin_find(h, table);
> >  	if (!t)
> >  		return NULL;
> 
> This chunk below...
> 
> > -	if (h->table[t->type].chain_cache)
> > +	if (h->have_cache && (!need_rules || h->have_rule_cache))
> >  		return h->table[t->type].chain_cache;
> > +
> > +	if (h->have_cache)
> > +		goto fetch_rules;
> 
> probably code above can be rework to this below?
> 
>         if (h->have_cache) {
>                 if (!need_rules || h->have_rule_cache)
>          		return h->table[t->type].chain_cache;
> 
> 		goto fetch_rules;
>         }
> 
> We can probably remove "goto fetch_rules;" and make this a function.
> 
>         if (h->have_cache) {
>                 if (!need_rules || h->have_rule_cache)
>          		return h->table[t->type].chain_cache;
> 
>                 fetch_rules_only();
>         } else {
>                 fetch_full();
>         }
> 
> so we use a bit less of goto for non-error path.

Yes, good points. I will do that.

[...]
> > -static struct nftnl_rule_list *nft_rule_list_get(struct nft_handle *h)
> > +static int nft_rule_list_update(struct nftnl_chain *c, void *data)
> 
> This nft_rule_list_update() looks like our former nft_rule_list_get().

Yes, I turned nft_rule_list_get() into a callback for use in
nft_chain_list_get(). The rename is more or less a side-effect of that.
I'll see if I can reuse code between nft_rule_list_get() and the
callback (if needed at all).

> >  {
> > +	struct nft_handle *h = data;
> >  	char buf[16536];
> >  	struct nlmsghdr *nlh;
> > -	struct nftnl_rule_list *list;
> > +	struct nftnl_rule *rule;
> >  	int ret;
> >  
> > -	if (h->rule_cache)
> > -		return h->rule_cache;
> > +	rule = nftnl_rule_alloc();
> > +	if (!rule)
> > +		return false;
> >  
> > -retry:
> > -	list = nftnl_rule_list_alloc();
> > -	if (list == NULL)
> > -		return 0;
> > +	nftnl_rule_set(rule, NFTNL_RULE_TABLE,
> > +		       nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE));
> 
> Use nftnl_rule_set_str() instead. I know this code it probably using
> this old interface (long time ago there was not nftnl_rule_set_str()).

Sure, will do.

[...]
> > -int nft_rule_save(struct nft_handle *h, const char *table, unsigned int format)
> > +static int nft_chain_save_rules(struct nft_handle *h,
> > +				struct nftnl_chain *c, unsigned int format)
> >  {
> > -	struct nftnl_rule_list *list;
> > -	struct nftnl_rule_list_iter *iter;
> > +	struct nftnl_rule_iter *iter;
> >  	struct nftnl_rule *r;
> >  
> > -	list = nft_rule_list_get(h);
> > -	if (list == NULL)
> > -		return 0;
> > -
> > -	iter = nftnl_rule_list_iter_create(list);
> > +	iter = nftnl_rule_iter_create(c);
> >  	if (iter == NULL)
> > -		return 0;
> > +		return 1;
> 
> Probably we can start using -1 for errors and 0 for success
> incrementally in this code? So we don't stick to the old/original
> iptables function error codes anymore from nft.c.

Fine with me! Keeping track which function has to return what is tedious
sometimes.

[...]
> > @@ -1553,6 +1584,7 @@ int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table,
> >  			fprintf(stdout, "Flushing chain `%s'\n", chain_name);
> >  
> >  		__nft_rule_flush(h, table, chain_name);
> > +		flush_rule_cache(c);
> 
> Place this flush_rule_cache() call in __nft_rule_flush() ?

The function is used from __nft_chain_user_flush() as well where no
cache flush happens.

[...]
> This chunk below could be well in a separated patch for easier review.
> 
> > -static int nft_is_expr_compatible(const struct nftnl_expr *expr)
> > +static bool nft_is_expr_compatible(const struct nftnl_expr *expr)

Yes, you're right. I had to adjust the is_*_compatible functions so
decided to make them a bit more consistent while doing so. I'll move the
return type change into a prepended patch.

[...]
> > --- a/iptables/xtables-restore.c
> > +++ b/iptables/xtables-restore.c
[...]
> > @@ -155,6 +155,9 @@ void xtables_restore_parse(struct nft_handle *h,
> >  					table);
> >  				if (cb->table_flush)
> >  					cb->table_flush(h, table);
> > +				/* fake rule cache presence so that we don't populate
> > +				 * the cache later with rules just flushed here */
> 
>                                 /* incrementally adding netdev comment styles
>                                  * would be good :-).
>                                  */

So comment ending on new line? I always forget what's the right comment
style in each project. :)

Thanks, Phil



[Index of Archives]     [Netfitler Users]     [LARTC]     [Bugtraq]     [Yosemite Forum]

  Powered by Linux