diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 7511918dce6f..49708e7e1339 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -833,7 +833,7 @@ static inline bool __nft_set_elem_expired(const struct nft_set_ext *ext, u64 tstamp) { if (!nft_set_ext_exists(ext, NFT_SET_EXT_TIMEOUT) || - nft_set_ext_timeout(ext)->timeout == 0) + READ_ONCE(nft_set_ext_timeout(ext)->timeout) == 0) return false; return time_after_eq64(tstamp, READ_ONCE(nft_set_ext_timeout(ext)->expiration)); @@ -1749,10 +1749,18 @@ struct nft_trans_table { #define nft_trans_table_update(trans) \ nft_trans_container_table(trans)->update +enum nft_trans_elem_flags { + NFT_TRANS_UPD_TIMEOUT = (1 << 0), + NFT_TRANS_UPD_EXPIRATION = (1 << 1), +}; + struct nft_trans_elem { struct nft_trans nft_trans; struct nft_set *set; struct nft_elem_priv *elem_priv; + u64 timeout; + u64 expiration; + u8 update_flags; bool bound; }; @@ -1762,6 +1770,12 @@ struct nft_trans_elem { nft_trans_container_elem(trans)->set #define nft_trans_elem_priv(trans) \ nft_trans_container_elem(trans)->elem_priv +#define nft_trans_elem_update_flags(trans) \ + nft_trans_container_elem(trans)->update_flags +#define nft_trans_elem_timeout(trans) \ + nft_trans_container_elem(trans)->timeout +#define nft_trans_elem_expiration(trans) \ + nft_trans_container_elem(trans)->expiration #define nft_trans_elem_set_bound(trans) \ nft_trans_container_elem(trans)->bound diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index ed85b10edb32..57259b5f3ef5 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -5815,7 +5815,7 @@ static int nf_tables_fill_setelem(struct sk_buff *skb, goto nla_put_failure; if (nft_set_ext_exists(ext, NFT_SET_EXT_TIMEOUT)) { - u64 timeout = nft_set_ext_timeout(ext)->timeout; + u64 timeout = READ_ONCE(nft_set_ext_timeout(ext)->timeout); u64 set_timeout = READ_ONCE(set->timeout); __be64 msecs = 0; @@ -6852,6 +6852,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, struct nft_data_desc desc; enum nft_registers dreg; struct nft_trans *trans; + u8 update_flags; u64 expiration; u64 timeout; int err, i; @@ -7163,8 +7164,30 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, nft_set_ext_exists(ext2, NFT_SET_EXT_OBJREF) && *nft_set_ext_obj(ext) != *nft_set_ext_obj(ext2))) goto err_element_clash; - else if (!(nlmsg_flags & NLM_F_EXCL)) + else if (!(nlmsg_flags & NLM_F_EXCL)) { err = 0; + if (nft_set_ext_exists(ext2, NFT_SET_EXT_TIMEOUT)) { + update_flags = 0; + if (timeout != nft_set_ext_timeout(ext2)->timeout) { + nft_trans_elem_timeout(trans) = timeout; + if (expiration == 0) + expiration = timeout; + + update_flags |= NFT_TRANS_UPD_TIMEOUT; + } + if (expiration) { + nft_trans_elem_expiration(trans) = expiration; + update_flags |= NFT_TRANS_UPD_EXPIRATION; + } + + if (update_flags) { + nft_trans_elem_priv(trans) = elem_priv; + nft_trans_elem_update_flags(trans) = update_flags; + nft_trans_commit_list_add_tail(ctx->net, trans); + goto err_elem_free; + } + } + } } else if (err == -ENOTEMPTY) { /* ENOTEMPTY reports overlapping between this element * and an existing one. @@ -10489,7 +10512,22 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) case NFT_MSG_NEWSETELEM: te = nft_trans_container_elem(trans); - nft_setelem_activate(net, te->set, te->elem_priv); + if (te->update_flags) { + const struct nft_set_ext *ext = + nft_set_elem_ext(te->set, te->elem_priv); + + if (te->update_flags & NFT_TRANS_UPD_TIMEOUT) { + WRITE_ONCE(nft_set_ext_timeout(ext)->timeout, + te->timeout); + } + if (te->update_flags & NFT_TRANS_UPD_EXPIRATION) { + WRITE_ONCE(nft_set_ext_timeout(ext)->expiration, + get_jiffies_64() + te->expiration); + } + } else { + nft_setelem_activate(net, te->set, te->elem_priv); + } + nf_tables_setelem_notify(&ctx, te->set, te->elem_priv, NFT_MSG_NEWSETELEM); @@ -10789,7 +10827,8 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) nft_trans_destroy(trans); break; case NFT_MSG_NEWSETELEM: - if (nft_trans_elem_set_bound(trans)) { + if (nft_trans_elem_update_flags(trans) || + nft_trans_elem_set_bound(trans)) { nft_trans_destroy(trans); break; } diff --git a/net/netfilter/nft_dynset.c b/net/netfilter/nft_dynset.c index 6a10305de24b..88922e0e8e83 100644 --- a/net/netfilter/nft_dynset.c +++ b/net/netfilter/nft_dynset.c @@ -95,7 +95,7 @@ void nft_dynset_eval(const struct nft_expr *expr, expr, regs, &ext)) { if (priv->op == NFT_DYNSET_OP_UPDATE && nft_set_ext_exists(ext, NFT_SET_EXT_TIMEOUT) && - nft_set_ext_timeout(ext)->timeout != 0) { + READ_ONCE(nft_set_ext_timeout(ext)->timeout) != 0) { timeout = priv->timeout ? : READ_ONCE(set->timeout); WRITE_ONCE(nft_set_ext_timeout(ext)->expiration, get_jiffies_64() + timeout); }