@@ -1237,12 +1237,29 @@ static void nft_chain_stats_replace(struct nft_base_chain *chain,
1237
1237
rcu_assign_pointer (chain -> stats , newstats );
1238
1238
}
1239
1239
1240
+ static void nf_tables_chain_free_chain_rules (struct nft_chain * chain )
1241
+ {
1242
+ struct nft_rule * * g0 = rcu_dereference_raw (chain -> rules_gen_0 );
1243
+ struct nft_rule * * g1 = rcu_dereference_raw (chain -> rules_gen_1 );
1244
+
1245
+ if (g0 != g1 )
1246
+ kvfree (g1 );
1247
+ kvfree (g0 );
1248
+
1249
+ /* should be NULL either via abort or via successful commit */
1250
+ WARN_ON_ONCE (chain -> rules_next );
1251
+ kvfree (chain -> rules_next );
1252
+ }
1253
+
1240
1254
static void nf_tables_chain_destroy (struct nft_ctx * ctx )
1241
1255
{
1242
1256
struct nft_chain * chain = ctx -> chain ;
1243
1257
1244
1258
BUG_ON (chain -> use > 0 );
1245
1259
1260
+ /* no concurrent access possible anymore */
1261
+ nf_tables_chain_free_chain_rules (chain );
1262
+
1246
1263
if (nft_is_base_chain (chain )) {
1247
1264
struct nft_base_chain * basechain = nft_base_chain (chain );
1248
1265
@@ -1335,6 +1352,27 @@ static void nft_chain_release_hook(struct nft_chain_hook *hook)
1335
1352
module_put (hook -> type -> owner );
1336
1353
}
1337
1354
1355
+ struct nft_rules_old {
1356
+ struct rcu_head h ;
1357
+ struct nft_rule * * start ;
1358
+ };
1359
+
1360
+ static struct nft_rule * * nf_tables_chain_alloc_rules (const struct nft_chain * chain ,
1361
+ unsigned int alloc )
1362
+ {
1363
+ if (alloc > INT_MAX )
1364
+ return NULL ;
1365
+
1366
+ alloc += 1 ; /* NULL, ends rules */
1367
+ if (sizeof (struct nft_rule * ) > INT_MAX / alloc )
1368
+ return NULL ;
1369
+
1370
+ alloc *= sizeof (struct nft_rule * );
1371
+ alloc += sizeof (struct nft_rules_old );
1372
+
1373
+ return kvmalloc (alloc , GFP_KERNEL );
1374
+ }
1375
+
1338
1376
static int nf_tables_addchain (struct nft_ctx * ctx , u8 family , u8 genmask ,
1339
1377
u8 policy , bool create )
1340
1378
{
@@ -1344,6 +1382,7 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
1344
1382
struct nft_stats __percpu * stats ;
1345
1383
struct net * net = ctx -> net ;
1346
1384
struct nft_chain * chain ;
1385
+ struct nft_rule * * rules ;
1347
1386
int err ;
1348
1387
1349
1388
if (table -> use == UINT_MAX )
@@ -1406,6 +1445,16 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
1406
1445
goto err1 ;
1407
1446
}
1408
1447
1448
+ rules = nf_tables_chain_alloc_rules (chain , 0 );
1449
+ if (!rules ) {
1450
+ err = - ENOMEM ;
1451
+ goto err1 ;
1452
+ }
1453
+
1454
+ * rules = NULL ;
1455
+ rcu_assign_pointer (chain -> rules_gen_0 , rules );
1456
+ rcu_assign_pointer (chain -> rules_gen_1 , rules );
1457
+
1409
1458
err = nf_tables_register_hook (net , table , chain );
1410
1459
if (err < 0 )
1411
1460
goto err1 ;
@@ -5850,21 +5899,162 @@ static void nf_tables_commit_release(struct net *net)
5850
5899
}
5851
5900
}
5852
5901
5902
+ static int nf_tables_commit_chain_prepare (struct net * net , struct nft_chain * chain )
5903
+ {
5904
+ struct nft_rule * rule ;
5905
+ unsigned int alloc = 0 ;
5906
+ int i ;
5907
+
5908
+ /* already handled or inactive chain? */
5909
+ if (chain -> rules_next || !nft_is_active_next (net , chain ))
5910
+ return 0 ;
5911
+
5912
+ rule = list_entry (& chain -> rules , struct nft_rule , list );
5913
+ i = 0 ;
5914
+
5915
+ list_for_each_entry_continue (rule , & chain -> rules , list ) {
5916
+ if (nft_is_active_next (net , rule ))
5917
+ alloc ++ ;
5918
+ }
5919
+
5920
+ chain -> rules_next = nf_tables_chain_alloc_rules (chain , alloc );
5921
+ if (!chain -> rules_next )
5922
+ return - ENOMEM ;
5923
+
5924
+ list_for_each_entry_continue (rule , & chain -> rules , list ) {
5925
+ if (nft_is_active_next (net , rule ))
5926
+ chain -> rules_next [i ++ ] = rule ;
5927
+ }
5928
+
5929
+ chain -> rules_next [i ] = NULL ;
5930
+ return 0 ;
5931
+ }
5932
+
5933
+ static void nf_tables_commit_chain_prepare_cancel (struct net * net )
5934
+ {
5935
+ struct nft_trans * trans , * next ;
5936
+
5937
+ list_for_each_entry_safe (trans , next , & net -> nft .commit_list , list ) {
5938
+ struct nft_chain * chain = trans -> ctx .chain ;
5939
+
5940
+ if (trans -> msg_type == NFT_MSG_NEWRULE ||
5941
+ trans -> msg_type == NFT_MSG_DELRULE ) {
5942
+ kvfree (chain -> rules_next );
5943
+ chain -> rules_next = NULL ;
5944
+ }
5945
+ }
5946
+ }
5947
+
5948
+ static void __nf_tables_commit_chain_free_rules_old (struct rcu_head * h )
5949
+ {
5950
+ struct nft_rules_old * o = container_of (h , struct nft_rules_old , h );
5951
+
5952
+ kvfree (o -> start );
5953
+ }
5954
+
5955
+ static void nf_tables_commit_chain_free_rules_old (struct nft_rule * * rules )
5956
+ {
5957
+ struct nft_rule * * r = rules ;
5958
+ struct nft_rules_old * old ;
5959
+
5960
+ while (* r )
5961
+ r ++ ;
5962
+
5963
+ r ++ ; /* rcu_head is after end marker */
5964
+ old = (void * ) r ;
5965
+ old -> start = rules ;
5966
+
5967
+ call_rcu (& old -> h , __nf_tables_commit_chain_free_rules_old );
5968
+ }
5969
+
5970
+ static void nf_tables_commit_chain_active (struct net * net , struct nft_chain * chain )
5971
+ {
5972
+ struct nft_rule * * g0 , * * g1 ;
5973
+ bool next_genbit ;
5974
+
5975
+ next_genbit = nft_gencursor_next (net );
5976
+
5977
+ g0 = rcu_dereference_protected (chain -> rules_gen_0 ,
5978
+ lockdep_nfnl_is_held (NFNL_SUBSYS_NFTABLES ));
5979
+ g1 = rcu_dereference_protected (chain -> rules_gen_1 ,
5980
+ lockdep_nfnl_is_held (NFNL_SUBSYS_NFTABLES ));
5981
+
5982
+ /* No changes to this chain? */
5983
+ if (chain -> rules_next == NULL ) {
5984
+ /* chain had no change in last or next generation */
5985
+ if (g0 == g1 )
5986
+ return ;
5987
+ /*
5988
+ * chain had no change in this generation; make sure next
5989
+ * one uses same rules as current generation.
5990
+ */
5991
+ if (next_genbit ) {
5992
+ rcu_assign_pointer (chain -> rules_gen_1 , g0 );
5993
+ nf_tables_commit_chain_free_rules_old (g1 );
5994
+ } else {
5995
+ rcu_assign_pointer (chain -> rules_gen_0 , g1 );
5996
+ nf_tables_commit_chain_free_rules_old (g0 );
5997
+ }
5998
+
5999
+ return ;
6000
+ }
6001
+
6002
+ if (next_genbit )
6003
+ rcu_assign_pointer (chain -> rules_gen_1 , chain -> rules_next );
6004
+ else
6005
+ rcu_assign_pointer (chain -> rules_gen_0 , chain -> rules_next );
6006
+
6007
+ chain -> rules_next = NULL ;
6008
+
6009
+ if (g0 == g1 )
6010
+ return ;
6011
+
6012
+ if (next_genbit )
6013
+ nf_tables_commit_chain_free_rules_old (g1 );
6014
+ else
6015
+ nf_tables_commit_chain_free_rules_old (g0 );
6016
+ }
6017
+
5853
6018
static int nf_tables_commit (struct net * net , struct sk_buff * skb )
5854
6019
{
5855
6020
struct nft_trans * trans , * next ;
5856
6021
struct nft_trans_elem * te ;
6022
+ struct nft_chain * chain ;
6023
+ struct nft_table * table ;
5857
6024
5858
- /* Bump generation counter, invalidate any dump in progress */
5859
- while (++ net -> nft .base_seq == 0 );
6025
+ /* 1. Allocate space for next generation rules_gen_X[] */
6026
+ list_for_each_entry_safe (trans , next , & net -> nft .commit_list , list ) {
6027
+ int ret ;
5860
6028
5861
- /* A new generation has just started */
5862
- net -> nft .gencursor = nft_gencursor_next (net );
6029
+ if (trans -> msg_type == NFT_MSG_NEWRULE ||
6030
+ trans -> msg_type == NFT_MSG_DELRULE ) {
6031
+ chain = trans -> ctx .chain ;
6032
+
6033
+ ret = nf_tables_commit_chain_prepare (net , chain );
6034
+ if (ret < 0 ) {
6035
+ nf_tables_commit_chain_prepare_cancel (net );
6036
+ return ret ;
6037
+ }
6038
+ }
6039
+ }
5863
6040
5864
- /* Make sure all packets have left the previous generation before
5865
- * purging old rules.
6041
+ /* step 2. Make rules_gen_X visible to packet path */
6042
+ list_for_each_entry (table , & net -> nft .tables , list ) {
6043
+ list_for_each_entry (chain , & table -> chains , list ) {
6044
+ if (!nft_is_active_next (net , chain ))
6045
+ continue ;
6046
+ nf_tables_commit_chain_active (net , chain );
6047
+ }
6048
+ }
6049
+
6050
+ /*
6051
+ * Bump generation counter, invalidate any dump in progress.
6052
+ * Cannot fail after this point.
5866
6053
*/
5867
- synchronize_rcu ();
6054
+ while (++ net -> nft .base_seq == 0 );
6055
+
6056
+ /* step 3. Start new generation, rules_gen_X now in use. */
6057
+ net -> nft .gencursor = nft_gencursor_next (net );
5868
6058
5869
6059
list_for_each_entry_safe (trans , next , & net -> nft .commit_list , list ) {
5870
6060
switch (trans -> msg_type ) {
0 commit comments