You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by yc...@apache.org on 2023/07/29 03:10:18 UTC

[cassandra] branch trunk updated: CIDR filtering authorizer

This is an automated email from the ASF dual-hosted git repository.

ycai pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 933137fb14 CIDR filtering authorizer
933137fb14 is described below

commit 933137fb14d4ebfe38d0d2c8e23b765aa1c89333
Author: Shailaja Koppu <s_...@apple.com>
AuthorDate: Tue Jun 13 17:18:29 2023 +0100

    CIDR filtering authorizer
    
    Patch by Shailaja Koppu, Yifan Cai; Reviewed by Dinesh Joshi, Yifan Cai for CASSANDRA-18592
    
    Co-Authored-By: Yifan Cai <yc...@apache.org>
---
 CHANGES.txt                                        |   1 +
 NEWS.txt                                           |   1 +
 conf/cassandra.yaml                                |  31 ++
 doc/cql3/CQL.textile                               |  17 +-
 .../examples/BNF/create_role_statement.bnf         |   2 +
 doc/modules/cassandra/examples/CQL/alter_role.cql  |   2 +
 doc/modules/cassandra/examples/CQL/create_role.cql |   2 +
 .../cassandra/examples/CQL/create_user_role.cql    |   3 +
 .../cassandra/pages/developing/cql/security.adoc   |  26 +-
 .../cassandra/pages/managing/virtualtables.adoc    |  29 ++
 pylib/cqlshlib/cql3handling.py                     |   2 +
 pylib/cqlshlib/test/test_cqlsh_completion.py       |   3 +
 src/antlr/Lexer.g                                  |   1 +
 src/antlr/Parser.g                                 |  35 +-
 .../cassandra/auth/AbstractCIDRAuthorizer.java     |  92 ++++
 .../cassandra/auth/AllowAllCIDRAuthorizer.java     |  73 +++
 src/java/org/apache/cassandra/auth/AuthConfig.java |   9 +
 .../org/apache/cassandra/auth/AuthKeyspace.java    |  34 +-
 .../apache/cassandra/auth/AuthenticatedUser.java   |  10 +
 .../cassandra/auth/CIDRGroupsMappingCache.java     | 135 ++++++
 .../auth/CIDRGroupsMappingIntervalTree.java        | 504 +++++++++++++++++++++
 .../cassandra/auth/CIDRGroupsMappingLoader.java    |  95 ++++
 .../cassandra/auth/CIDRGroupsMappingManager.java   | 251 ++++++++++
 .../auth/CIDRGroupsMappingManagerMBean.java        |  79 ++++
 .../cassandra/auth/CIDRGroupsMappingTable.java     |  80 ++++
 .../org/apache/cassandra/auth/CIDRPermissions.java | 257 +++++++++++
 .../cassandra/auth/CIDRPermissionsCache.java       |  69 +++
 .../cassandra/auth/CIDRPermissionsManager.java     | 220 +++++++++
 .../auth/CIDRPermissionsManagerMBean.java          |  37 +-
 .../cassandra/auth/CassandraCIDRAuthorizer.java    | 157 +++++++
 .../org/apache/cassandra/auth/ICIDRAuthorizer.java | 112 +++++
 src/java/org/apache/cassandra/config/Config.java   |   2 +
 .../cassandra/config/DatabaseDescriptor.java       |  69 +++
 src/java/org/apache/cassandra/cql3/CIDR.java       | 270 +++++++++++
 .../cql3/statements/AlterRoleStatement.java        |  22 +-
 .../cql3/statements/CreateRoleStatement.java       |  18 +-
 .../cql3/statements/DropRoleStatement.java         |   1 +
 .../db/virtual/CIDRFilteringMetricsTable.java      | 256 +++++++++++
 .../db/virtual/CIDRFilteringMetricsTableMBean.java |  41 +-
 .../cassandra/db/virtual/SystemViewsKeyspace.java  |   1 +
 .../cassandra/metrics/CIDRAuthorizerMetrics.java   |  97 ++++
 .../org/apache/cassandra/service/ClientState.java  |   5 +
 .../apache/cassandra/service/StorageService.java   |   5 +-
 .../org/apache/cassandra/tools/ListCIDRGroups.java |  52 +++
 src/java/org/apache/cassandra/tools/NodeProbe.java | 126 ++++++
 src/java/org/apache/cassandra/tools/NodeTool.java  |   7 +
 .../tools/nodetool/CIDRFilteringStats.java         |  93 ++++
 .../cassandra/tools/nodetool/DropCIDRGroup.java    |  50 ++
 .../tools/nodetool/GetCIDRGroupsOfIP.java          |  50 ++
 .../nodetool/InvalidateCIDRPermissionsCache.java   |  56 +++
 .../tools/nodetool/ReloadCIDRGroupsCache.java      |  37 +-
 .../cassandra/tools/nodetool/UpdateCIDRGroup.java  |  57 +++
 .../distributed/test/jmx/JMXGetterCheckTest.java   |   3 +-
 .../test/microbench/CIDRAuthorizerBench.java       | 156 +++++++
 .../test/microbench/LongestMatchingCIDRBench.java  | 141 ++++++
 test/unit/org/apache/cassandra/SchemaLoader.java   |   6 +-
 .../cassandra/auth/AllowAllCIDRAuthorizerTest.java | 172 +++++++
 .../org/apache/cassandra/auth/AuthTestUtils.java   | 151 ++++++
 .../auth/CIDRGroupsMappingIntervalTreeTest.java    | 232 ++++++++++
 .../auth/CIDRGroupsMappingManagerTest.java         | 164 +++++++
 .../cassandra/auth/CIDRGroupsMappingTableTest.java | 220 +++++++++
 .../CassandraCIDRAuthorizerEnforceModeTest.java    | 300 ++++++++++++
 .../CassandraCIDRAuthorizerMonitorModeTest.java    | 138 ++++++
 .../auth/CassandraNetworkAuthorizerTest.java       |  38 +-
 test/unit/org/apache/cassandra/auth/RolesTest.java |   3 +-
 .../config/DatabaseDescriptorRefTest.java          |   2 +
 test/unit/org/apache/cassandra/cql3/CIDRTest.java  | 194 ++++++++
 test/unit/org/apache/cassandra/cql3/CQLTester.java |   2 +
 .../cql3/statements/AddIdentityStatementTest.java  |   3 +-
 .../cql3/statements/AlterRoleStatementTest.java    |  46 +-
 .../cql3/statements/CreateRoleStatementTest.java   |  39 +-
 .../cql3/statements/CreateUserStatementTest.java   |  14 +-
 .../db/virtual/CIDRFilteringMetricsTableTest.java  | 212 +++++++++
 .../tools/nodetool/CIDRFilteringStatsTest.java     | 136 ++++++
 .../tools/nodetool/DropCIDRGroupTest.java          | 142 ++++++
 .../tools/nodetool/GetCIDRGroupsOfIPTest.java      | 126 ++++++
 .../InvalidateCIDRPermissionsCacheTest.java        | 168 +++++++
 .../tools/nodetool/ListCIDRGroupTest.java          | 129 ++++++
 .../tools/nodetool/ReloadCIDRGroupsCacheTest.java  | 135 ++++++
 .../tools/nodetool/UpdateCIDRGroupTest.java        | 166 +++++++
 .../cassandra/tools/nodetool/VerifyTest.java       |   3 +-
 81 files changed, 6787 insertions(+), 138 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 7a60b6ae95..ef4749b12c 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 5.0
+ * CIDR filtering authorizer (CASSANDRA-18592)
  * Remove 3.x from the versions checked for prepared statement behaviour (CASSANDRA-18695)
  * Add vector similarity functions (CASSANDRA-18640)
  * Lift MessagingService.minimum_version to 40 (CASSANDRA-18314)
diff --git a/NEWS.txt b/NEWS.txt
index 378ae6e93c..9b8441f857 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -71,6 +71,7 @@ using the provided 'sstableupgrade' tool.
 
 New features
 ------------
+    - Added a new authorizer, CIDR authorizer, to restrict user access based on CIDR groups.
     - Pluggable crypto providers were made possible via `crypto_provider` section in cassandra.yaml. The default provider is 
       Amazon Corretto Crypto Provider and it is installed automatically upon node's start. Only x86_64 and aarch64 architectures are supported now.
       Please consult upgrade section to know more details when upgrading from older Cassandra versions.
diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml
index 8783f53252..26d3579f03 100644
--- a/conf/cassandra.yaml
+++ b/conf/cassandra.yaml
@@ -189,6 +189,37 @@ role_manager: CassandraRoleManager
 #   increase system_auth keyspace replication factor if you use this authorizer.
 network_authorizer: AllowAllNetworkAuthorizer
 
+# CIDR authorization backend, implementing ICIDRAuthorizer; used to restrict user
+# access from certain CIDRs
+# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllCIDRAuthorizer,
+# CassandraCIDRAuthorizer}.
+# - AllowAllCIDRAuthorizer allows access from any CIDR to any user - set it to disable CIDR authorization.
+# - CassandraCIDRAuthorizer stores user's CIDR permissions in system_auth.cidr_permissions table. Please
+#   increase system_auth keyspace replication factor if you use this authorizer, otherwise any changes to
+#   system_auth tables being used by this feature may be lost when a host goes down.
+cidr_authorizer:
+  class_name: AllowAllCIDRAuthorizer
+  # Below parameters are used only when CIDR authorizer is enabled
+  # parameters:
+    # CIDR authorizer when enabled, i.e, CassandraCIDRAuthorizer, is applicable for non-superusers only by default.
+    # Set this setting to true, to enable CIDR authorization for superusers as well.
+    # Note: CIDR checks cannot be performed for JMX calls
+    # cidr_checks_for_superusers: true
+
+    # CIDR authorizer when enabled, supports MONITOR and ENFORCE modes. Default mode is MONITOR
+    # In MONITOR mode, CIDR checks are NOT enforced. Instead, CIDR groups of users accesses are logged using
+    # nospamlogger. A warning message would be logged if a user accesses from unauthorized CIDR group (but access won't
+    # be rejected). An info message would be logged otherwise.
+    # In ENFORCE mode, CIDR checks are enforced, i.e, users accesses would be rejected if attempted from unauthorized
+    # CIDR groups.
+    # cidr_authorizer_mode: MONITOR
+
+    # Refresh interval for CIDR groups cache, this value is considered in minutes
+    # cidr_groups_cache_refresh_interval: 5
+
+    # Maximum number of entries an IP to CIDR groups cache can accommodate
+    # ip_cache_max_size: 100
+
 # Depending on the auth strategy of the cluster, it can be beneficial to iterate
 # from root to table (root -> ks -> table) instead of table to root (table -> ks -> root).
 # As the auth entries are whitelisting, once a permission is found you know it to be
diff --git a/doc/cql3/CQL.textile b/doc/cql3/CQL.textile
index 4f6a4d7338..88918f87d9 100644
--- a/doc/cql3/CQL.textile
+++ b/doc/cql3/CQL.textile
@@ -1283,6 +1283,10 @@ bc(syntax)..
            | LOGIN = <boolean>
            | SUPERUSER = <boolean>
            | OPTIONS = <map_literal>
+           | ACCESS TO DATACENTERS { dcPermission (, dcPermission)* }
+           | ACCESS TO ALL DATACENTERS
+           | ACCESS FROM CIDRS { cidrPermission (, cidrPermission)* }
+           | ACCESS FROM ALL CIDRS
 p. 
 
 __Sample:__
@@ -1292,6 +1296,9 @@ CREATE ROLE new_role;
 CREATE ROLE alice WITH PASSWORD = 'password_a' AND LOGIN = true;
 CREATE ROLE bob WITH PASSWORD = 'password_b' AND LOGIN = true AND SUPERUSER = true;
 CREATE ROLE carlos WITH OPTIONS = { 'custom_option1' : 'option1_value', 'custom_option2' : 99 };
+CREATE ROLE rob WITH LOGIN = true and PASSWORD = 'password_c' AND ACCESS FROM ALL CIDRS;
+CREATE ROLE hob WITH LOGIN = true and PASSWORD = 'password_d' AND ACCESS FROM CIDRS { 'region1' };
+
 
 By default roles do not possess @LOGIN@ privileges or @SUPERUSER@ status.
 
@@ -1299,7 +1306,7 @@ By default roles do not possess @LOGIN@ privileges or @SUPERUSER@ status.
 
 If a role has the @LOGIN@ privilege, clients may identify as that role when connecting. For the duration of that connection, the client will acquire any roles and privileges granted to that role.
 
-Only a client with with the @CREATE@ permission on the database roles resource may issue @CREATE ROLE@ requests (see the "relevant section":#permissions below), unless the client is a @SUPERUSER@. Role management in Cassandra is pluggable and custom implementations may support only a subset of the listed options.
+Only a client with the @CREATE@ permission on the database roles resource may issue @CREATE ROLE@ requests (see the "relevant section":#permissions below), unless the client is a @SUPERUSER@. Role management in Cassandra is pluggable and custom implementations may support only a subset of the listed options.
 
 Role names should be quoted if they contain non-alphanumeric characters. 
 
@@ -1327,12 +1334,18 @@ bc(syntax)..
            | LOGIN = <boolean>
            | SUPERUSER = <boolean>
            | OPTIONS = <map_literal>
+           | ACCESS TO DATACENTERS { dcPermission (, dcPermission)* }
+           | ACCESS TO ALL DATACENTERS
+           | ACCESS FROM CIDRS { cidrPermission (, cidrPermission)* }
+           | ACCESS FROM ALL CIDRS
 p. 
 
 __Sample:__
 
 bc(sample). 
 ALTER ROLE bob WITH PASSWORD = 'PASSWORD_B' AND SUPERUSER = false;
+ALTER ROLE rob WITH LOGIN = true and PASSWORD = 'password_c' AND ACCESS FROM ALL CIDRS;
+ALTER ROLE hob WITH LOGIN = true and PASSWORD = 'password_d' AND ACCESS FROM CIDRS { 'region1' };
 
 Conditions on executing @ALTER ROLE@ statements:
 
@@ -2493,6 +2506,7 @@ CQL distinguishes between _reserved_ and _non-reserved_ keywords. Reserved keywo
 | @BY@           | yes |
 | @CALLED@       | no  |
 | @CAST@         | no  |
+| @CIDRS@        | no  |
 | @CLUSTERING@   | no  |
 | @COLUMNFAMILY@ | yes |
 | @COMPACT@      | no  |
@@ -2647,6 +2661,7 @@ h3. 3.4.6
 * Allow to grant permission for all tables in a keyspace (see "CASSANDRA-17027":https://issues.apache.org/jira/browse/CASSANDRA-17027).
 * Allow to use pure monotonic functions on the last attribute of the @GROUP BY@ clause (see "CASSANDRA-11871":https://issues.apache.org/jira/browse/CASSANDRA-11871).
 * Add floor function to allow grouping by time range (see "CASSANDRA-11871":https://issues.apache.org/jira/browse/CASSANDRA-11871).
+* Add support for adding and updating CIDR permissions using @CREATE ROLE@ and @ALTER ROLE@ statements
 
 h3. 3.4.5
 
diff --git a/doc/modules/cassandra/examples/BNF/create_role_statement.bnf b/doc/modules/cassandra/examples/BNF/create_role_statement.bnf
index 4236cc6ee2..8e4e0cda80 100644
--- a/doc/modules/cassandra/examples/BNF/create_role_statement.bnf
+++ b/doc/modules/cassandra/examples/BNF/create_role_statement.bnf
@@ -8,3 +8,5 @@ role_option ::= PASSWORD '=' string
                 | OPTIONS '=' map_literal
                 | ACCESS TO DATACENTERS set_literal
                 | ACCESS TO ALL DATACENTERS
+                | ACCESS FROM CIDRS set_literal
+                | ACCESS FROM ALL CIDRS
diff --git a/doc/modules/cassandra/examples/CQL/alter_role.cql b/doc/modules/cassandra/examples/CQL/alter_role.cql
index 1e858aea23..516262152c 100644
--- a/doc/modules/cassandra/examples/CQL/alter_role.cql
+++ b/doc/modules/cassandra/examples/CQL/alter_role.cql
@@ -1,2 +1,4 @@
 ALTER ROLE bob WITH PASSWORD = 'PASSWORD_B' AND SUPERUSER = false;
 ALTER ROLE bob WITH HASHED PASSWORD = '$2a$10$JSJEMFm6GeaW9XxT5JIheuEtPvat6i7uKbnTcxX3c1wshIIsGyUtG' AND SUPERUSER = false;
+ALTER ROLE rob WITH LOGIN = true and PASSWORD = 'password_c' AND ACCESS FROM ALL CIDRS;
+ALTER ROLE hob WITH LOGIN = true and PASSWORD = 'password_d' AND ACCESS FROM CIDRS { 'region1' };
diff --git a/doc/modules/cassandra/examples/CQL/create_role.cql b/doc/modules/cassandra/examples/CQL/create_role.cql
index 2ceee54d62..593556039e 100644
--- a/doc/modules/cassandra/examples/CQL/create_role.cql
+++ b/doc/modules/cassandra/examples/CQL/create_role.cql
@@ -5,3 +5,5 @@ CREATE ROLE bob WITH PASSWORD = 'password_b' AND LOGIN = true AND SUPERUSER = tr
 CREATE ROLE carlos WITH OPTIONS = { 'custom_option1' : 'option1_value', 'custom_option2' : 99 };
 CREATE ROLE alice WITH PASSWORD = 'password_a' AND LOGIN = true AND ACCESS TO DATACENTERS {'DC1', 'DC3'};
 CREATE ROLE alice WITH PASSWORD = 'password_a' AND LOGIN = true AND ACCESS TO ALL DATACENTERS;
+CREATE ROLE bob WITH LOGIN = true and PASSWORD = 'password_d' AND ACCESS FROM CIDRS { 'region1', 'region2' };
+CREATE ROLE hob WITH LOGIN = true and PASSWORD = 'password_c' AND ACCESS FROM ALL CIDRS;
diff --git a/doc/modules/cassandra/examples/CQL/create_user_role.cql b/doc/modules/cassandra/examples/CQL/create_user_role.cql
index 810f76ca9c..ff44c75a51 100644
--- a/doc/modules/cassandra/examples/CQL/create_user_role.cql
+++ b/doc/modules/cassandra/examples/CQL/create_user_role.cql
@@ -12,3 +12,6 @@ CREATE ROLE alice WITH PASSWORD = 'password_a' AND LOGIN = true;
 
 CREATE USER alice WITH PASSWORD 'password_a';
 CREATE ROLE alice WITH PASSWORD = 'password_a' AND LOGIN = true;
+
+CREATE ROLE rob WITH LOGIN = true and PASSWORD = 'password_c' AND ACCESS FROM ALL CIDRS;
+CREATE ROLE hob WITH LOGIN = true and PASSWORD = 'password_d' AND ACCESS FROM CIDRS { 'region1' };
diff --git a/doc/modules/cassandra/pages/developing/cql/security.adoc b/doc/modules/cassandra/pages/developing/cql/security.adoc
index 166eb870a3..1c9a89ff30 100644
--- a/doc/modules/cassandra/pages/developing/cql/security.adoc
+++ b/doc/modules/cassandra/pages/developing/cql/security.adoc
@@ -69,6 +69,18 @@ specifiying datacenters implicitly grants access to all datacenters. The
 clause `ACCESS TO ALL DATACENTERS` can be used for explicitness, but
 there's no functional difference.
 
+==== Restricting connections from specific CIDR groups
+
+If a `cidr_authorizer` has been configured, you can restrict roles to login
+only from specific regions, aka CIDR groups, with the `ACCESS FROM CIDRS` clause
+followed by a set literal of CIDR groups the user can access from. Not
+specifying CIDR groups implicitly grants access from all CIDR groups. The
+clause `ACCESS FROM ALL CIDRS` can be used for explicitness, but there's no
+functional difference. This clause can be also be used to remove any CIDR
+groups restrictions. Valid CIDR groups should be used with `ACCESS FROM CIDRS`
+clause. `nodetool list-cidrgroups` command can be used to see available CIDR groups
+in the Cluster.
+
 ==== Creating a role conditionally
 
 Attempting to create an existing role results in an invalid query
@@ -108,7 +120,19 @@ followed by a set literal of datacenters the user can access. To remove
 any data center restrictions, use the `ACCESS TO ALL DATACENTERS`
 clause.
 
-Conditions on executing `ALTER ROLE` statements:
+==== Restricting connections from specific CIDR groups
+
+If a `cidr_authorizer` has been configured, you can restrict roles to login
+only from specific regions, aka CIDR groups, with the `ACCESS FROM CIDRS` clause
+followed by a set literal of CIDR groups the user can access from. Not
+specifying CIDR groups implicitly grants access from all CIDR groups. The
+clause `ACCESS FROM ALL CIDRS` can be used for explicitness, but there's no
+functional difference. This clause can be also be used to remove any CIDR
+groups restrictions. Valid CIDR groups should be used with `ACCESS FROM CIDRS`
+clause. `nodetool list-cidrgroups` command can be used to see available CIDR groups
+in the Cluster.
+
+==== Conditions on executing `ALTER ROLE` statements:
 
 * a client must have `SUPERUSER` status to alter the `SUPERUSER` status
 of another role
diff --git a/doc/modules/cassandra/pages/managing/virtualtables.adoc b/doc/modules/cassandra/pages/managing/virtualtables.adoc
index 22262c1b14..f43f4d7c00 100644
--- a/doc/modules/cassandra/pages/managing/virtualtables.adoc
+++ b/doc/modules/cassandra/pages/managing/virtualtables.adoc
@@ -66,6 +66,10 @@ The following table describes the virtual tables:
 |caches |Displays the general cache information including cache name, capacity_bytes, entry_count, hit_count, hit_ratio double,
 recent_hit_rate_per_second, recent_request_rate_per_second, request_count, and size_bytes.
 
+|cidr_filtering_metrics_counts |Counts metrics specific to CIDR filtering.
+
+|cidr_filtering_metrics_latencies |Latencies metrics specific to CIDR filtering.
+
 |clients |Lists information about all connected clients.
 
 |coordinator_read_latency |Records counts, keyspace_name, table_name, max, median, and per_second for coordinator reads.
@@ -244,6 +248,31 @@ counters |       26214400 |           0 |         0 |       NaN |
 (4 rows)
 ....
 
+
+=== CIDR filtering metrics Virtual Tables
+The `cidr_filtering_metrics_counts` virtual table lists counts metrics specific to CIDR filtering. A query on `cidr_filtering_metrics_counts` virtual table lists metrics similar to below.
+
+....
+cqlsh> select * from system_views.cidr_filtering_metrics_counts;
+ name                                                   | value
+--------------------------------------------------------+-------
+                         CIDR groups cache reload count |     2
+ Number of CIDR accesses accepted from CIDR group - aws |    15
+ Number of CIDR accesses accepted from CIDR group - gcp |    30
+ Number of CIDR accesses rejected from CIDR group - gcp |     6
+....
+
+The `cidr_filtering_metrics_latencies` virtual table lists latencies metrics specific to CIDR filtering. A query on `cidr_filtering_metrics_latencies` virtual table lists below metrics.
+
+....
+cqlsh> select * from system_views.cidr_filtering_metrics_latencies;
+ name                                        | max   | p50th | p95th | p999th | p99th
+---------------------------------------------+-------+-------+-------+--------+-------
+                    CIDR checks latency (ns) | 24601 |     1 | 11864 |  24601 | 24601
+       CIDR groups cache reload latency (ns) | 42510 | 42510 | 42510 |  42510 | 42510
+ Lookup IP in CIDR groups cache latency (ns) |     1 |     1 |     1 |      1 |     1
+....
+
 === CQL metrics Virtual Table
 The `cql_metrics` virtual table lists metrics specific to CQL prepared statement caching. A query on `cql_metrics` virtual table lists below metrics.
 
diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py
index 7ff252be32..4c81926ce4 100644
--- a/pylib/cqlshlib/cql3handling.py
+++ b/pylib/cqlshlib/cql3handling.py
@@ -1498,6 +1498,8 @@ syntax_rules += r'''
                  | "LOGIN" "=" <boolean>
                  | "ACCESS" "TO" "DATACENTERS" <setLiteral>
                  | "ACCESS" "TO" "ALL" "DATACENTERS"
+                 | "ACCESS" "FROM" "CIDRS" <setLiteral>
+                 | "ACCESS" "FROM" "ALL" "CIDRS"
                  ;
 
 <dropRoleStatement> ::= "DROP" "ROLE" ("IF" "EXISTS")? <rolename>
diff --git a/pylib/cqlshlib/test/test_cqlsh_completion.py b/pylib/cqlshlib/test/test_cqlsh_completion.py
index b0f0b87a1c..db59297a97 100644
--- a/pylib/cqlshlib/test/test_cqlsh_completion.py
+++ b/pylib/cqlshlib/test/test_cqlsh_completion.py
@@ -998,12 +998,15 @@ class TestCqlshCompletion(CqlshCompletionCase):
         self.trycompletions('CREATE ROLE foo WITH HASHED ', immediate='PASSWORD = ');
         self.trycompletions('CREATE ROLE foo WITH ACCESS TO ', choices=['ALL', 'DATACENTERS'])
         self.trycompletions('CREATE ROLE foo WITH ACCESS TO ALL ', immediate='DATACENTERS ')
+        self.trycompletions('CREATE ROLE foo WITH ACCESS FROM ', choices=['ALL', 'CIDRS'])
+        self.trycompletions('CREATE ROLE foo WITH ACCESS FROM ALL ', immediate='CIDRS ')
 
     def test_complete_in_alter_role(self):
         self.trycompletions('ALTER ROLE ', choices=['<identifier>', 'IF', '<quotedName>'])
         self.trycompletions('ALTER ROLE foo ', immediate='WITH ')
         self.trycompletions('ALTER ROLE foo WITH ', choices=['ACCESS', 'HASHED', 'LOGIN', 'OPTIONS', 'PASSWORD', 'SUPERUSER'])
         self.trycompletions('ALTER ROLE foo WITH ACCESS TO ', choices=['ALL', 'DATACENTERS'])
+        self.trycompletions('ALTER ROLE foo WITH ACCESS FROM ', choices=['ALL', 'CIDRS'])
 
     def test_complete_in_drop_role(self):
         self.trycompletions('DROP ROLE ', choices=['<identifier>', 'IF', '<quotedName>'])
diff --git a/src/antlr/Lexer.g b/src/antlr/Lexer.g
index c75523ed74..77e1202e4c 100644
--- a/src/antlr/Lexer.g
+++ b/src/antlr/Lexer.g
@@ -156,6 +156,7 @@ K_NOLOGIN:     N O L O G I N;
 K_OPTIONS:     O P T I O N S;
 K_ACCESS:      A C C E S S;
 K_DATACENTERS: D A T A C E N T E R S;
+K_CIDRS:       C I D R S;
 K_IDENTITY:    I D E N T I T Y;
 
 K_CLUSTERING:  C L U S T E R I N G;
diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g
index 0d2150481d..a2d43b7392 100644
--- a/src/antlr/Parser.g
+++ b/src/antlr/Parser.g
@@ -1204,7 +1204,7 @@ createUserStatement returns [CreateRoleStatement stmt]
         {
            throw new SyntaxException("Options 'password' and 'hashed password' are mutually exclusive");
         }
-        $stmt = new CreateRoleStatement(name, opts, DCPermissions.all(), ifNotExists); }
+        $stmt = new CreateRoleStatement(name, opts, DCPermissions.all(), CIDRPermissions.all(), ifNotExists); }
     ;
 
 /**
@@ -1225,7 +1225,7 @@ alterUserStatement returns [AlterRoleStatement stmt]
          {
             throw new SyntaxException("Options 'password' and 'hashed password' are mutually exclusive");
          }
-         $stmt = new AlterRoleStatement(name, opts, null, ifExists);
+         $stmt = new AlterRoleStatement(name, opts, null, null, ifExists);
       }
     ;
 
@@ -1277,15 +1277,20 @@ listUsersStatement returns [ListRolesStatement stmt]
  *  SUPERUSER = (true|false)
  *  LOGIN = (true|false)
  *  OPTIONS = { 'k1':'v1', 'k2':'v2'}
+ *  ACCESS TO ALL DATACENTERS
+ *  ACCESS TO DATACENTERS { dcPermission (, dcPermission)* }
+ *  ACCESS FROM ALL CIDRS
+ *  ACCESS FROM CIDRS { cidrPermission (, cidrPermission)* }
  */
 createRoleStatement returns [CreateRoleStatement stmt]
     @init {
         RoleOptions opts = new RoleOptions();
         DCPermissions.Builder dcperms = DCPermissions.builder();
+        CIDRPermissions.Builder cidrperms = CIDRPermissions.builder();
         boolean ifNotExists = false;
     }
     : K_CREATE K_ROLE (K_IF K_NOT K_EXISTS { ifNotExists = true; })? name=userOrRoleName
-      ( K_WITH roleOptions[opts, dcperms] )?
+      ( K_WITH roleOptions[opts, dcperms, cidrperms] )?
       {
         // set defaults if they weren't explictly supplied
         if (!opts.getLogin().isPresent())
@@ -1300,7 +1305,7 @@ createRoleStatement returns [CreateRoleStatement stmt]
         {
             throw new SyntaxException("Options 'password' and 'hashed password' are mutually exclusive");
         }
-        $stmt = new CreateRoleStatement(name, opts, dcperms.build(), ifNotExists);
+        $stmt = new CreateRoleStatement(name, opts, dcperms.build(), cidrperms.build(), ifNotExists);
       }
     ;
 
@@ -1312,21 +1317,26 @@ createRoleStatement returns [CreateRoleStatement stmt]
  *  SUPERUSER = (true|false)
  *  LOGIN = (true|false)
  *  OPTIONS = { 'k1':'v1', 'k2':'v2'}
+ *  ACCESS TO ALL DATACENTERS
+ *  ACCESS TO DATACENTERS { dcPermission (, dcPermission)* }
+ *  ACCESS FROM ALL CIDRS
+ *  ACCESS FROM CIDRS { cidrPermission (, cidrPermission)* }
  */
 alterRoleStatement returns [AlterRoleStatement stmt]
     @init {
         RoleOptions opts = new RoleOptions();
         DCPermissions.Builder dcperms = DCPermissions.builder();
+        CIDRPermissions.Builder cidrperms = CIDRPermissions.builder();
         boolean ifExists = false;
     }
     : K_ALTER K_ROLE (K_IF K_EXISTS { ifExists = true; })? name=userOrRoleName
-      ( K_WITH roleOptions[opts, dcperms] )?
+      ( K_WITH roleOptions[opts, dcperms, cidrperms] )?
       {
          if (opts.getPassword().isPresent() && opts.getHashedPassword().isPresent())
          {
             throw new SyntaxException("Options 'password' and 'hashed password' are mutually exclusive");
          }
-         $stmt = new AlterRoleStatement(name, opts, dcperms.isModified() ? dcperms.build() : null, ifExists);
+         $stmt = new AlterRoleStatement(name, opts, dcperms.isModified() ? dcperms.build() : null, cidrperms.isModified() ? cidrperms.build() : null, ifExists);
       }
     ;
 
@@ -1355,11 +1365,11 @@ listRolesStatement returns [ListRolesStatement stmt]
       { $stmt = new ListRolesStatement(grantee, recursive); }
     ;
 
-roleOptions[RoleOptions opts, DCPermissions.Builder dcperms]
-    : roleOption[opts, dcperms] (K_AND roleOption[opts, dcperms])*
+roleOptions[RoleOptions opts, DCPermissions.Builder dcperms, CIDRPermissions.Builder cidrperms]
+    : roleOption[opts, dcperms, cidrperms] (K_AND roleOption[opts, dcperms, cidrperms])*
     ;
 
-roleOption[RoleOptions opts, DCPermissions.Builder dcperms]
+roleOption[RoleOptions opts, DCPermissions.Builder dcperms, CIDRPermissions.Builder cidrperms]
     :  K_PASSWORD '=' v=STRING_LITERAL { opts.setOption(IRoleManager.Option.PASSWORD, $v.text); }
     |  K_HASHED K_PASSWORD '=' v=STRING_LITERAL { opts.setOption(IRoleManager.Option.HASHED_PASSWORD, $v.text); }
     |  K_OPTIONS '=' m=fullMapLiteral { opts.setOption(IRoleManager.Option.OPTIONS, convertPropertyMap(m)); }
@@ -1367,12 +1377,18 @@ roleOption[RoleOptions opts, DCPermissions.Builder dcperms]
     |  K_LOGIN '=' b=BOOLEAN { opts.setOption(IRoleManager.Option.LOGIN, Boolean.valueOf($b.text)); }
     |  K_ACCESS K_TO K_ALL K_DATACENTERS { dcperms.all(); }
     |  K_ACCESS K_TO K_DATACENTERS '{' dcPermission[dcperms] (',' dcPermission[dcperms])* '}'
+    |  K_ACCESS K_FROM K_ALL K_CIDRS { cidrperms.all(); }
+    |  K_ACCESS K_FROM K_CIDRS '{' cidrPermission[cidrperms] (',' cidrPermission[cidrperms])* '}'
     ;
 
 dcPermission[DCPermissions.Builder builder]
     : dc=STRING_LITERAL { builder.add($dc.text); }
     ;
 
+cidrPermission[CIDRPermissions.Builder builder]
+    : cidr=STRING_LITERAL { builder.add($cidr.text); }
+    ;
+
 // for backwards compatibility in CREATE/ALTER USER, this has no '='
 userPassword[RoleOptions opts]
     :  K_PASSWORD v=STRING_LITERAL { opts.setOption(IRoleManager.Option.PASSWORD, $v.text); }
@@ -1987,6 +2003,7 @@ basic_unreserved_keyword returns [String str]
         | K_PARTITION
         | K_GROUP
         | K_DATACENTERS
+        | K_CIDRS
         | K_ACCESS
         | K_DEFAULT
         | K_MBEAN
diff --git a/src/java/org/apache/cassandra/auth/AbstractCIDRAuthorizer.java b/src/java/org/apache/cassandra/auth/AbstractCIDRAuthorizer.java
new file mode 100644
index 0000000000..a2d591318e
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/AbstractCIDRAuthorizer.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.metrics.CIDRAuthorizerMetrics;
+
+/**
+ * Abstract CIDR authorizer, contains code common to all implementations of ICIDRAuthorizer
+ */
+public abstract class AbstractCIDRAuthorizer implements ICIDRAuthorizer
+{
+    protected static CIDRPermissionsManager cidrPermissionsManager;
+    protected static CIDRGroupsMappingManager cidrGroupsMappingManager;
+
+    protected static CIDRAuthorizerMetrics cidrAuthorizerMetrics;
+
+    @VisibleForTesting
+    void createManagers()
+    {
+        // CIDRPermissionsManager provides functionality to retrieve or update CIDR permissions for a role
+        cidrPermissionsManager = new CIDRPermissionsManager();
+
+        // CIDRGroupsMappingManager provides functionality to retrieve or update CIDR groups mapping table
+        cidrGroupsMappingManager = new CIDRGroupsMappingManager();
+    }
+
+    protected void commonSetup()
+    {
+        createManagers();
+
+        cidrPermissionsManager.setup();
+        cidrGroupsMappingManager.setup();
+
+        cidrAuthorizerMetrics = new CIDRAuthorizerMetrics();
+    }
+
+    @Override
+    public CIDRGroupsMappingManager getCidrGroupsMappingManager()
+    {
+        return cidrGroupsMappingManager;
+    }
+
+    @Override
+    public CIDRAuthorizerMetrics getCidrAuthorizerMetrics() { return cidrAuthorizerMetrics; }
+
+    @Override
+    public boolean requireAuthorization()
+    {
+        return true;
+    }
+
+    @Override
+    public void setCidrGroupsForRole(RoleResource role, CIDRPermissions cidrPermissions)
+    {
+        // Allow 'create role' and 'alter role' CQL commands with ACCESS FROM CIDRS clause in both cases,
+        // when feature is enabled and also disabled
+        cidrPermissionsManager.setCidrGroupsForRole(role, cidrPermissions);
+    }
+
+    @Override
+    public void dropCidrPermissionsForRole(RoleResource role)
+    {
+        // Allow dropping of CIDR permissions for roles in both cases,
+        // when feature is enabled and also disabled
+        cidrPermissionsManager.drop(role);
+    }
+
+    @Override
+    public void validateConfiguration() throws ConfigurationException
+    {
+        // do nothing
+    }
+}
diff --git a/src/java/org/apache/cassandra/auth/AllowAllCIDRAuthorizer.java b/src/java/org/apache/cassandra/auth/AllowAllCIDRAuthorizer.java
new file mode 100644
index 0000000000..394b798bfb
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/AllowAllCIDRAuthorizer.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetAddress;
+import java.util.Set;
+
+import org.apache.cassandra.exceptions.InvalidRequestException;
+
+/**
+ * AllowAllCIDRAuthorizer allows any user to access from any CIDR
+ * i.e, disables CIDR authorization
+ */
+public class AllowAllCIDRAuthorizer extends AbstractCIDRAuthorizer
+{
+    public void setup()
+    {
+        commonSetup();
+    }
+
+    @Override
+    public void initCaches()
+    {
+        // Caches not created when CIDR authorization is disabled
+    }
+
+    @Override
+    public boolean requireAuthorization()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean invalidateCidrPermissionsCache(String roleName)
+    {
+        throw new InvalidRequestException("Invalidate CIDR permissions cache operation not supported by " + getClass().getSimpleName());
+    }
+
+    @Override
+    public void loadCidrGroupsCache()
+    {
+        throw new InvalidRequestException("Load CIDR groups cache operation not supported by " + getClass().getSimpleName());
+    }
+
+    @Override
+    public Set<String> lookupCidrGroupsForIp(InetAddress ip)
+    {
+        throw new InvalidRequestException("'Get CIDR groups for IP' operation not supported by " + getClass().getSimpleName());
+    }
+
+    @Override
+    public boolean hasAccessFromIp(RoleResource role, InetAddress ipAddress)
+    {
+        // Allow all accesses
+        return true;
+    }
+}
diff --git a/src/java/org/apache/cassandra/auth/AuthConfig.java b/src/java/org/apache/cassandra/auth/AuthConfig.java
index 5cecd6f130..786459d1ce 100644
--- a/src/java/org/apache/cassandra/auth/AuthConfig.java
+++ b/src/java/org/apache/cassandra/auth/AuthConfig.java
@@ -115,6 +115,14 @@ public final class AuthConfig
             throw new ConfigurationException(conf.network_authorizer + " can't be used with " + conf.authenticator.class_name, false);
         }
 
+        // cidr authorizer
+        ICIDRAuthorizer cidrAuthorizer = ICIDRAuthorizer.newCIDRAuthorizer(conf.cidr_authorizer);
+        DatabaseDescriptor.setCIDRAuthorizer(cidrAuthorizer);
+        if (cidrAuthorizer.requireAuthorization() && !authenticator.requireAuthentication())
+        {
+            throw new ConfigurationException(conf.cidr_authorizer + " can't be used with " + conf.authenticator, false);
+        }
+
         // Validate at last to have authenticator, authorizer, role-manager and internode-auth setup
         // in case these rely on each other.
 
@@ -122,6 +130,7 @@ public final class AuthConfig
         authorizer.validateConfiguration();
         roleManager.validateConfiguration();
         networkAuthorizer.validateConfiguration();
+        cidrAuthorizer.validateConfiguration();
         DatabaseDescriptor.getInternodeAuthenticator().validateConfiguration();
     }
 }
diff --git a/src/java/org/apache/cassandra/auth/AuthKeyspace.java b/src/java/org/apache/cassandra/auth/AuthKeyspace.java
index b1616e07fa..b495c90419 100644
--- a/src/java/org/apache/cassandra/auth/AuthKeyspace.java
+++ b/src/java/org/apache/cassandra/auth/AuthKeyspace.java
@@ -58,8 +58,13 @@ public final class AuthKeyspace
     public static final String ROLE_PERMISSIONS = "role_permissions";
     public static final String RESOURCE_ROLE_INDEX = "resource_role_permissons_index";
     public static final String NETWORK_PERMISSIONS = "network_permissions";
+    public static final String CIDR_PERMISSIONS = "cidr_permissions";
+    public static final String CIDR_GROUPS = "cidr_groups";
     public static final String IDENTITY_TO_ROLES = "identity_to_role";
-    public static final Set<String> TABLE_NAMES = ImmutableSet.of(ROLES, ROLE_MEMBERS, ROLE_PERMISSIONS, RESOURCE_ROLE_INDEX, NETWORK_PERMISSIONS, IDENTITY_TO_ROLES);
+    public static final Set<String> TABLE_NAMES = ImmutableSet.of(ROLES, ROLE_MEMBERS, ROLE_PERMISSIONS,
+                                                                  RESOURCE_ROLE_INDEX, NETWORK_PERMISSIONS,
+                                                                  CIDR_PERMISSIONS, CIDR_GROUPS,
+                                                                  IDENTITY_TO_ROLES);
 
     public static final long SUPERUSER_SETUP_DELAY = SUPERUSER_SETUP_DELAY_MS.getLong();
 
@@ -116,6 +121,28 @@ public final class AuthKeyspace
               + "dcs frozen<set<text>>, "
               + "PRIMARY KEY(role))");
 
+    public static final String CIDR_PERMISSIONS_TBL_ROLE_COL_NAME = "role";
+    public static final String CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME = "cidr_groups";
+    private static final TableMetadata CIDRPermissions =
+    parse(CIDR_PERMISSIONS,
+          "user cidr permissions",
+          "CREATE TABLE %s ("
+          + CIDR_PERMISSIONS_TBL_ROLE_COL_NAME + " text, "
+          + CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME + " frozen<set<text>>, "
+          + "PRIMARY KEY(" + CIDR_PERMISSIONS_TBL_ROLE_COL_NAME + "))"
+    );
+
+    public static final String CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME = "cidr_group";
+    public static final String CIDR_GROUPS_TBL_CIDRS_COL_NAME = "cidrs";
+    private static final TableMetadata CIDRGroups =
+    parse(CIDR_GROUPS,
+          "cidr groups to cidrs mapping",
+          "CREATE TABLE %s ("
+          + CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME + " text, "
+          + CIDR_GROUPS_TBL_CIDRS_COL_NAME + " frozen<set<tuple<inet, smallint>>>, "
+          + "PRIMARY KEY(" + CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME + "))"
+    );
+
     private static TableMetadata parse(String name, String description, String cql)
     {
         return CreateTableStatement.parse(format(cql, name), SchemaConstants.AUTH_KEYSPACE_NAME)
@@ -129,6 +156,9 @@ public final class AuthKeyspace
     {
         return KeyspaceMetadata.create(SchemaConstants.AUTH_KEYSPACE_NAME,
                                        KeyspaceParams.simple(Math.max(DEFAULT_RF, DatabaseDescriptor.getDefaultKeyspaceRF())),
-                                       Tables.of(Roles, RoleMembers, RolePermissions, ResourceRoleIndex, NetworkPermissions, IdentityToRoles));
+                                       Tables.of(Roles, RoleMembers, RolePermissions,
+                                                 ResourceRoleIndex, NetworkPermissions,
+                                                 CIDRPermissions, CIDRGroups,
+                                                 IdentityToRoles));
     }
 }
diff --git a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
index 620a1d2ded..6b31110e95 100644
--- a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
+++ b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java
@@ -17,6 +17,7 @@
  */
 package org.apache.cassandra.auth;
 
+import java.net.InetSocketAddress;
 import java.util.Set;
 import com.google.common.base.Objects;
 
@@ -41,11 +42,15 @@ public class AuthenticatedUser
     public static final PermissionsCache permissionsCache = new PermissionsCache(DatabaseDescriptor.getAuthorizer());
     public static final NetworkPermissionsCache networkPermissionsCache = new NetworkPermissionsCache(DatabaseDescriptor.getNetworkAuthorizer());
 
+    private static final ICIDRAuthorizer cidrAuthorizer = DatabaseDescriptor.getCIDRAuthorizer();
+
     /** Use {@link AuthCacheService#initializeAndRegisterCaches} rather than calling this directly */
     public static void init()
     {
         AuthCacheService.instance.register(permissionsCache);
         AuthCacheService.instance.register(networkPermissionsCache);
+
+        cidrAuthorizer.initCaches();
     }
 
     private final String name;
@@ -146,6 +151,11 @@ public class AuthenticatedUser
         return networkPermissionsCache.get(this.getPrimaryRole()).canAccess(Datacenters.thisDatacenter());
     }
 
+    public boolean hasAccessFromIp(InetSocketAddress remoteAddress)
+    {
+        return cidrAuthorizer.hasAccessFromIp(role, remoteAddress.getAddress());
+    }
+
     @Override
     public String toString()
     {
diff --git a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingCache.java b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingCache.java
new file mode 100644
index 0000000000..5f7546459f
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingCache.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetAddress;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.base.Preconditions;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import org.apache.cassandra.concurrent.ImmediateExecutor;
+import org.apache.cassandra.concurrent.ScheduledExecutors;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.metrics.CIDRAuthorizerMetrics;
+import org.apache.cassandra.utils.MonotonicClock;
+
+/**
+ * CIDR groups mapping cache. Periodically reloads the cache with the latest entries
+ * in the table {@link AuthKeyspace#CIDR_GROUPS}
+ */
+public class CIDRGroupsMappingCache
+{
+    // References to the latest mapping, changes everytime cache is reloaded
+    private volatile CIDRGroupsMappingLoader currentCidrGroupsMappingLoader = null;
+
+    // IP to CIDR groups cache, to reuse CIDR groups lookup result for an IP
+    private LoadingCache<InetAddress, Set<String>> ipToCidrGroupsCache;
+
+    private volatile boolean cacheInitialized = false;
+
+    private CIDRGroupsMappingManager cidrGroupsMappingManager;
+    private final CIDRAuthorizerMetrics cidrAuthorizerMetrics;
+
+    public CIDRGroupsMappingCache(CIDRGroupsMappingManager cidrGroupsMappingManager,
+                                  CIDRAuthorizerMetrics cidrAuthorizerMetrics)
+    {
+        this.cidrGroupsMappingManager = cidrGroupsMappingManager;
+        this.cidrAuthorizerMetrics = cidrAuthorizerMetrics;
+
+        // IP to CIDR groups cache
+        ipToCidrGroupsCache = Caffeine.newBuilder()
+                                      .maximumSize(DatabaseDescriptor.getIpCacheMaxSize())
+                                      .executor(ImmediateExecutor.INSTANCE)
+                                      .build(this::lookupCidrGroupsCacheForIp);
+
+        // Interval to reload the cache periodically
+        int cidrGroupsCacheRefreshInterval = DatabaseDescriptor.getCidrGroupsCacheRefreshInterval();
+        // Schedule a task to reload cache periodically
+        ScheduledExecutors.scheduledTasks.scheduleWithFixedDelay(this::reloadCidrGroupsCache,
+                                                                 cidrGroupsCacheRefreshInterval,
+                                                                 cidrGroupsCacheRefreshInterval,
+                                                                 TimeUnit.MINUTES);
+    }
+
+    /**
+     * Builds CIDR groups cache from entries in the table {@link AuthKeyspace#CIDR_GROUPS}
+     * and points to the latest cache
+     */
+    private void loadCidrGroupsCacheInternal()
+    {
+        long startTimeNanos = MonotonicClock.Global.approxTime.now();
+
+        currentCidrGroupsMappingLoader = new CIDRGroupsMappingLoader(this.cidrGroupsMappingManager);
+        // Clear IP to CIDR groups cache entries as we reloaded CIDR groups cache
+        ipToCidrGroupsCache.invalidateAll();
+
+        cidrAuthorizerMetrics.cacheReloadCount.inc();
+        cidrAuthorizerMetrics.cacheReloadLatency.update(MonotonicClock.Global.approxTime.now() - startTimeNanos,
+                                                        TimeUnit.NANOSECONDS);
+    }
+
+    /**
+     * Reload CIDR groups cache with the latest state of the table {@link AuthKeyspace#CIDR_GROUPS}
+     */
+    private void reloadCidrGroupsCache()
+    {
+        // Wait until CIDR authorizer calls init cache during the setup
+        // Otherwise scheduled task may trigger this before startup is complete
+        // and fails to find system tables
+        if (cacheInitialized)
+            loadCidrGroupsCacheInternal();
+    }
+
+    /**
+     * Builds CIDR groups cache the first time. Should be called before
+     * auth starts sending lookup IP requests
+     */
+    public void loadCidrGroupsCache()
+    {
+        loadCidrGroupsCacheInternal();
+        cacheInitialized = true;
+    }
+
+    private Set<String> lookupCidrGroupsCacheForIp(InetAddress ipAddr)
+    {
+        long startTimeNanos = MonotonicClock.Global.approxTime.now();
+
+        Preconditions.checkNotNull(currentCidrGroupsMappingLoader);
+        Set<String> cidrGroups = currentCidrGroupsMappingLoader.lookupCidrGroupsCacheForIp(ipAddr);
+
+        cidrAuthorizerMetrics.lookupCidrGroupsForIpLatency.update(MonotonicClock.Global.approxTime.now() - startTimeNanos,
+                                                                  TimeUnit.NANOSECONDS);
+
+        return cidrGroups;
+    }
+
+    /**
+     * Lookup best matching CIDR group for the given IP
+     * @param ipAddr IP address
+     * @return returns set of CIDR group(s)
+     */
+    public Set<String> lookupCidrGroupsForIp(InetAddress ipAddr)
+    {
+        Preconditions.checkState(cacheInitialized, "CIDR groups cache not inited");
+        return ipToCidrGroupsCache.get(ipAddr);
+    }
+}
diff --git a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingIntervalTree.java b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingIntervalTree.java
new file mode 100644
index 0000000000..2dcb652b1e
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingIntervalTree.java
@@ -0,0 +1,504 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import org.apache.cassandra.cql3.CIDR;
+
+/**
+ * This class implements CIDR Interval tree and the ability to find longest matching CIDR for the given IP.
+ * CIDRs interval tree is a variant of interval tree. Each node contains a CIDR and a value.
+ * A node has left children array and the right children array.
+ * - The left children's CIDRs are either less than the starting IP of parent or overlaps with the parent node.
+ * - The right children's CIDRs are either greater than the ending IP of the parent or overlaps with the parent node.
+ * Note that nodes that overlap with the parent node are included in both left and right children arrays.
+ *
+ * The tree organizes nodes by placing non-overlapping CIDRs at the same level. In general, CIDRs with the same net mask
+ * do not overlap, hence are placed in the same level. CIDRs with different net mask may overlap, hence placed at
+ * different levels in the tree. In addition to this, there is an optimisation to promote a CIDR to an upper level, if
+ * it is not overlapping with any CIDR in the parent level, that means, in such cases a CIDR with different net mask can
+ * co-locate in the same level with other CIDRs.
+ *
+ * Levels closer to the root contains CIDRs with higher net mask value. Net mask value decreases as levels further down
+ * from the root. i.e, Nearer the level to the root, the narrower the CIDR, meaning matching the longer IP prefix.
+ *
+ * Search for Longest matching CIDR for an IP starts at level 0, if not found a match, search continues to the next
+ * level, until it finds a match or reaches leaf nodes without a match. That means search terminates on the first match
+ * closest to the root, i.e, locates narrowest matching CIDR.
+ *
+ * Example:
+ * Assume below CIDRs
+ * "128.10.120.2/10", ==> IP range 128.0.0.0 - 128.63.255.255, netmask 10
+ * "128.20.120.2/20", ==> IP range 128.20.112.0 - 128.20.127.255, netmask 20
+ * "0.0.0.0/0",       ==> IP range 0.0.0.0 - 255.255.255.255, netmask 0
+ * "10.1.1.2/10"      ==> IP range 10.0.0.0 - 10.63.255.255, netmask 10
+ *
+ * Resulting interval tree looks like:
+ *
+ * (10.0.0.0 - 10.63.255.255, 10)  (128.20.112.0 - 128.20.127.255, 20)
+ *               /             \               / \
+ *              /              (128.0.0.0 - 128.63.255.255, 10)
+ *             /                      /  \
+ *            (0.0.0.0 - 255.255.255.255, 0)
+ *
+ * Note that in this example (10.0.0.0 - 10.63.255.255, 10) doesn't have any overlapping CIDR, hence moved up a level as
+ * an optimization
+ */
+public class CIDRGroupsMappingIntervalTree<V> implements CIDRGroupsMappingTable<V>
+{
+    private final IPIntervalTree<V> tree;
+
+    /**
+     * Build an interval tree for given CIDRs
+     * @param isIPv6 true if this CIDR groups mapping is for IPv6 IPs, false otherwise
+     * @param cidrMappings CIDR to Value mappings
+     */
+    public CIDRGroupsMappingIntervalTree(boolean isIPv6, Map<CIDR, Set<V>> cidrMappings)
+    {
+        for (CIDR cidr : cidrMappings.keySet())
+        {
+            if (isIPv6 != cidr.isIPv6())
+                throw new IllegalArgumentException("Invalid CIDR format, expecting " + getIPTypeString(isIPv6) +
+                                                   ", received " + getIPTypeString(cidr.isIPv6()));
+        }
+
+        this.tree = IPIntervalTree.build(new ArrayList<>(cidrMappings
+                                                         .entrySet()
+                                                         .stream()
+                                                         .collect(Collectors.groupingBy(p -> p.getKey().getNetMask(),
+                                                                                        TreeMap::new,
+                                                                                        Collectors.toList()))
+                                                         .descendingMap()
+                                                         .values()));
+    }
+
+    /**
+     * Get the longest matching CIDR (i.e, the narrowest match) for given IP
+     * @param ip IP to lookup CIDR group
+     * @return CIDR group name(s)
+     */
+    public Set<V> lookupLongestMatchForIP(InetAddress ip)
+    {
+        // Valid when empty CIDR groups mappings received, i.e, cidr_groups table is empty
+        if (tree == null)
+            return Collections.emptySet();
+
+        return tree.query(ip);
+    }
+
+    /**
+     * This class represents a node of an IP interval tree.
+     * A node contains a CIDR, value associated with it, left and right children
+     */
+    static class IPIntervalNode<V>
+    {
+        private final CIDR cidr;
+        private final Set<V> values = new HashSet<>();
+        private IPIntervalNode<V>[] left;
+        private IPIntervalNode<V>[] right;
+
+        public IPIntervalNode(CIDR cidr, Set<V> values, IPIntervalNode<V>[] children)
+        {
+            this.cidr = cidr;
+            if (values != null)
+                this.values.addAll(values);
+            updateChildren(children, true, true);
+        }
+
+        @VisibleForTesting
+        CIDR cidr()
+        {
+            return cidr;
+        }
+
+        @VisibleForTesting
+        IPIntervalNode<V>[] left()
+        {
+            return left;
+        }
+
+        @VisibleForTesting
+        IPIntervalNode<V>[] right()
+        {
+            return right;
+        }
+
+        private void updateLeft(IPIntervalNode<V>[] newValue, boolean shouldUpdate)
+        {
+            if (shouldUpdate)
+                this.left = newValue;
+        }
+
+        private void updateRight(IPIntervalNode<V>[] newValue, boolean shouldUpdate)
+        {
+            if (shouldUpdate)
+                this.right = newValue;
+        }
+
+        /**
+         * Split the children array according to the IP range of this node, and link the nodes to either
+         * the left or right children.
+         * @param children array of nodes which are children for this node
+         * @param updateLeft true to update left children, false otherwise
+         * @param updateRight true to update right children, false otherwise
+         */
+        private void updateChildren(IPIntervalNode<V>[] children, boolean updateLeft, boolean updateRight)
+        {
+            // this is leaf node
+            if (children == null)
+            {
+                updateLeft(null, updateLeft);
+                updateRight(null, updateRight);
+                return;
+            }
+
+            // Find the node in the children that is the closest to this node.
+            int index = binarySearchNodesIndex(children, this.cidr.getStartIpAddress());
+            IPIntervalNode<V> closest = children[index];
+
+            // Scenario - all children nodes are greater than this node
+            if (index == 0 && CIDR.compareIPs(this.cidr.getEndIpAddress(), closest.cidr.getStartIpAddress()) < 0)
+            {
+                updateLeft(null, updateLeft);
+                updateRight(children, updateRight);
+            }
+            // Scenario - all children nodes are lower than this node
+            else if (index == children.length - 1 &&
+                     CIDR.compareIPs(this.cidr.getStartIpAddress(), closest.cidr.getEndIpAddress()) > 0)
+            {
+                updateLeft(children, updateLeft);
+                updateRight(null, updateRight);
+            }
+            else // Scenario - part of the children nodes are lower, and the other are greater
+            {
+                // When this node does not overlap with the closest, split the array and
+                // link left and right children correspondingly.
+                if (CIDR.compareIPs(this.cidr.getStartIpAddress(), closest.cidr.getEndIpAddress()) > 0)
+                {
+                    // including the closest (node at index) in left
+                    updateLeft(Arrays.copyOfRange(children, 0, index + 1), updateLeft);
+                    // put the rest in right
+                    updateRight(Arrays.copyOfRange(children, index + 1, children.length), updateRight);
+                }
+                else // When the node overlaps, include the closest node in both its left and right children nodes.
+                {
+                    // The parent node overlaps with at most 1 interval in the children, because of nature of the CIDR.
+                    // Increasing the bit mask by 1, divides the range into halfs.
+                    // Note that the node@index is included in both left and right
+                    // it is because the current interval partially overlaps with the closest interval
+                    // The overlapping interval should always be searched if we cannot find an exact match with current interval.
+                    updateLeft(Arrays.copyOfRange(children, 0, index + 1), updateLeft);
+                    updateRight(Arrays.copyOfRange(children, index, children.length), updateRight);
+                }
+            }
+        }
+
+        private void updateLeftIfNull(IPIntervalNode<V>[] children)
+        {
+            if (left != null)
+                return;
+
+            updateChildren(children, true, false);
+        }
+
+        private void updateRightIfNull(IPIntervalNode<V>[] children)
+        {
+            if (right != null)
+                return;
+
+            updateChildren(children, false, true);
+        }
+
+        /**
+         * Binary search given array of nodes and return index of the closest matching node.
+         * It looks up for the interval that matches exactly or is left (lower) to the ip.
+         * @param nodes array of nodes
+         * @param ip    IP address to search
+         * @param <V>   data type of the value
+         * @return index of the closest node
+         */
+        static <V> int binarySearchNodesIndex(IPIntervalNode<V>[] nodes, InetAddress ip)
+        {
+            int start = 0; // inclusive
+            int end = nodes.length; // exclusive
+
+            while (true)
+            {
+                if (start >= end)
+                {
+                    // return the closest
+                    return Math.max((end - 1), 0);
+                }
+
+                int mid = start + (end - start) / 2;
+                IPIntervalNode<V> midNode = nodes[mid];
+                int cmp = CIDR.compareIPs(ip, midNode.cidr.getStartIpAddress());
+
+                if (cmp == 0) // found the node
+                {
+                    return mid;
+                }
+                else if (cmp < 0) // Given IP is less than middle node's starting IP, search left side sub array
+                {
+                    end = mid;
+                }
+                else  // Given IP is >= middle node's starting IP, so compare ending IP
+                {
+                    int compEnd = CIDR.compareIPs(ip, midNode.cidr.getEndIpAddress());
+                    // Given IP is >= middle node's starting IP and <= than the ending IP, found the match
+                    if (compEnd <= 0)
+                    {
+                        return mid;
+                    }
+                    else // IP > middle node's end IP >= given IP, search right side sub array
+                    {
+                        start = mid + 1;
+                    }
+                }
+            }
+        }
+
+        /**
+         * Binary search given array of nodes to find the closest IP interval to the input IP
+         * @param nodes array of nodes
+         * @param ip    IP address to search
+         * @param <V>   data type of the value
+         * @return the closest node to the input IP
+         */
+        static <V> IPIntervalNode<V> binarySearchNodes(IPIntervalNode<V>[] nodes, InetAddress ip)
+        {
+            int index = binarySearchNodesIndex(nodes, ip);
+            return nodes[index];
+        }
+
+        /**
+         * Search the tree for a CIDR matching given IP. Uses DFS and stops on first match i.e, finds the closest match
+         * to the root, which is the narrowest matching CIDR
+         * @param root subtree with this node as root
+         * @param ip   IP address to search
+         * @param <V>  data type of the value
+         * @return value(s) associated with the CIDR matching the given IP, Returns null if no match found
+         */
+        static <V> Set<V> query(IPIntervalNode<V> root, InetAddress ip)
+        {
+            IPIntervalNode<V> current = root;
+            while (true) // while loop transformed from tail recursion
+            {
+                boolean largerThanStart = CIDR.compareIPs(ip, current.cidr.getStartIpAddress()) >= 0;
+                boolean lessThanEnd = CIDR.compareIPs(ip, current.cidr.getEndIpAddress()) <= 0;
+                if (largerThanStart && lessThanEnd)
+                {
+                    return current.values;
+                }
+                else
+                {
+                    IPIntervalNode<V>[] candidates = largerThanStart ? current.right : current.left;
+                    // the tree is exhausted, and we are unable to find a match
+                    if (candidates == null)
+                    {
+                        return null;
+                    }
+                    current = binarySearchNodes(candidates, ip);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * This class represents an interval tree for CIDRs
+     * @param <V> data type of the value
+     */
+    static class IPIntervalTree<V>
+    {
+        // References to first level nodes of the tree
+        private final IPIntervalNode<V>[] level0;
+        // depth of the tree
+        private final int depth;
+
+        private IPIntervalTree(IPIntervalNode<V>[] nodes, int depth)
+        {
+            this.level0 = nodes;
+            this.depth = depth;
+        }
+
+        @VisibleForTesting
+        int getDepth()
+        {
+            return depth;
+        }
+
+        /**
+         * Optimize levels by moving non-overlapping CIDRs from lower level to the upper level. Levels are updated in-place
+         * This optimization moves CIDRs closer to the root, hence improves the search to find IP nearer to the root,
+         * i.e, avoiding going depth during the search
+         * @param upperLevel level for CIDRs with higher netmask value
+         * @param lowerLevel level for CIDRs with lower netmask value
+         * @param <V>        data type of value
+         */
+        private static <V> void optimizeLevels(List<Map.Entry<CIDR, V>> upperLevel, List<Map.Entry<CIDR, V>> lowerLevel)
+        {
+            List<Map.Entry<CIDR, V>> newUpper = new ArrayList<>(upperLevel.size() + lowerLevel.size());
+            newUpper.addAll(upperLevel);
+
+            List<Map.Entry<CIDR, V>> newLower = new ArrayList<>(lowerLevel.size());
+
+            for (int i = 0; i < lowerLevel.size(); i++)
+            {
+                boolean noOverlap = true;
+                for (int j = 0; j < upperLevel.size(); j++)
+                {
+                    if (CIDR.overlaps(lowerLevel.get(i).getKey(), upperLevel.get(j).getKey()))
+                    {
+                        // overlapping node remains in lower level
+                        newLower.add(lowerLevel.get(i));
+                        noOverlap = false;
+                        break;
+                    }
+                }
+
+                // the node from lower level does not overlap with any node in upper level, so move it up
+                if (noOverlap)
+                {
+                    newUpper.add(lowerLevel.get(i));
+                }
+            }
+
+            upperLevel.clear();
+            lowerLevel.clear();
+            upperLevel.addAll(newUpper);
+            lowerLevel.addAll(newLower);
+        }
+
+        /**
+         * Optimize by lifting lower level non-overlapping CIDRs to the upper levels.
+         * The method modifies cidrsGroupedByNetMasks in-place.
+         * @param cidrsGroupedByNetMasks CIDRs grouped by netmask, levels in the order of higher netmask to lower netmask value
+         * @param <V>                    data type of the value
+         */
+        private static <V> void optimizeAllLevels(List<List<Map.Entry<CIDR, Set<V>>>> cidrsGroupedByNetMasks)
+        {
+            for (int i = 0; i < cidrsGroupedByNetMasks.size(); i++)
+            {
+                List<Map.Entry<CIDR, Set<V>>> current = cidrsGroupedByNetMasks.get(0);
+                for (int j = i + 1; j < cidrsGroupedByNetMasks.size(); j++)
+                {
+                    List<Map.Entry<CIDR, Set<V>>> lower = cidrsGroupedByNetMasks.get(j);
+                    optimizeLevels(current, lower);
+                }
+            }
+        }
+
+        /**
+         * Link the nodes between levels. If a node in the lower level has no left or right children,
+         * the nodes from higher levels are lifted and linked as the left or right children.
+         * @param cidrMappings list of CIDR to value mappings
+         * @param result       2 dimension array that stores all (linked) nodes in the tree. result[0] is the first level in the tree.
+         * @param startIndex   starting level in the result array, to link it to its children nodes
+         * @param <V>          data type of the value
+         */
+        @SuppressWarnings("unchecked")
+        private static <V> void linkNodes(List<List<Map.Entry<CIDR, Set<V>>>> cidrMappings,
+                                          IPIntervalNode<V>[][] result,
+                                          int startIndex)
+        {
+            List<Map.Entry<CIDR, Set<V>>> cidrsAtLevel = cidrMappings.get(startIndex);
+            int next = startIndex + 1;
+            IPIntervalNode<V>[] lowerLevel = next == result.length ? null : result[next];
+            result[startIndex] = cidrsAtLevel
+            .stream()
+            .map(pair -> {
+                CIDR cidr = pair.getKey();
+                Set<V> value = pair.getValue();
+                IPIntervalNode<V> node = new IPIntervalNode<>(cidr, value, lowerLevel);
+                // Cannot link its left or right children to the next level nodes.
+                // Try the nodes in the even lower levels.
+                if (next + 1 < result.length && (node.left == null || node.right == null))
+                {
+                    for (int i = next + 1; i < result.length; i++)
+                    {
+                        node.updateLeftIfNull(result[i]);
+                        node.updateRightIfNull(result[i]);
+
+                        // the node has both left and right children, it is good and no further lifting is needed.
+                        if (node.left != null && node.right != null)
+                        {
+                            break;
+                        }
+                    }
+                }
+                return node;
+            })
+            // sort by ipStart. Remember the interval do not overlap
+            .sorted(Comparator.comparing(n -> n.cidr.getStartIpAddress(), CIDR::compareIPs))
+            .toArray(IPIntervalNode[]::new);
+        }
+
+        /**
+         * Build an interval tree for given CIDRs
+         * @param cidrsGroupedByNetMasks CIDRs grouped by netmask, levels in the order of higher netmask to lower netmask value
+         * @param <V>                    data type of the value
+         * @return returns reference to the interval tree, returns null if input is empty
+         */
+        @SuppressWarnings("unchecked")
+        public static <V> IPIntervalTree<V> build(List<List<Map.Entry<CIDR, Set<V>>>> cidrsGroupedByNetMasks)
+        {
+            if (cidrsGroupedByNetMasks.isEmpty())
+                return null;
+
+            optimizeAllLevels(cidrsGroupedByNetMasks);
+
+            // After optimization, trim empty levels if exists
+            cidrsGroupedByNetMasks.removeIf(List::isEmpty);
+
+            // Create an array for each level
+            IPIntervalNode<V>[][] result = new IPIntervalNode[cidrsGroupedByNetMasks.size()][];
+            // build the tree bottom up, i.e, add CIDRs in the order of smaller netmask value to higher netmask value
+            for (int i = cidrsGroupedByNetMasks.size() - 1; i >= 0; i--)
+            {
+                linkNodes(cidrsGroupedByNetMasks, result, i);
+            }
+            return new IPIntervalTree<>(result[0], cidrsGroupedByNetMasks.size());
+        }
+
+        /**
+         * Search interval tree for the longest matching CIDR for the given IP
+         * @param ip IP address to search
+         * @return Value(s) associated with matching CIDR
+         */
+        public Set<V> query(InetAddress ip)
+        {
+            IPIntervalNode<V> closest = IPIntervalNode.binarySearchNodes(level0, ip);
+            return IPIntervalNode.query(closest, ip);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingLoader.java b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingLoader.java
new file mode 100644
index 0000000000..42ba904b28
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingLoader.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.utils.Pair;
+
+/**
+ * Reads the table {@link AuthKeyspace#CIDR_GROUPS} and populates CIDR groups mapping data structures.
+ * Maintains separate mappings for IPv4 and IPv6 CIDRs
+ */
+public class CIDRGroupsMappingLoader
+{
+    private static final Logger logger = LoggerFactory.getLogger(CIDRGroupsMappingLoader.class);
+
+    CIDRGroupsMappingTable.Builder<String> ipv4CidrGroupsTableBuilder = CIDRGroupsMappingTable.builder(false);
+    CIDRGroupsMappingTable.Builder<String> ipv6CidrGroupsTableBuilder = CIDRGroupsMappingTable.builder(true);
+
+    // Maintain separate mappings for IPv4 and IPv6 CIDRs
+    private CIDRGroupsMappingTable<String> ipv4CidrGroupsTable = null;
+    private CIDRGroupsMappingTable<String> ipv6CidrGroupsTable = null;
+
+    protected CIDRGroupsMappingManager cidrGroupsMappingManager;
+
+    public CIDRGroupsMappingLoader(CIDRGroupsMappingManager cidrGroupsMappingManager)
+    {
+        this.cidrGroupsMappingManager = cidrGroupsMappingManager;
+
+        populateCidrGroupsMapping();
+    }
+
+    private void populateCidrGroupsMapping()
+    {
+        UntypedResultSet rows = cidrGroupsMappingManager.getCidrGroupsTableEntries();
+        for (UntypedResultSet.Row row : rows)
+        {
+            String cidrGroupName = row.getString(AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME);
+            Set<Pair<InetAddress, Short>> cidrs = cidrGroupsMappingManager.retrieveCidrsFromRow(row);
+
+            for (Pair<InetAddress, Short> cidr : cidrs)
+            {
+                try
+                {
+                    CIDR validCidr = new CIDR(cidr.left(), cidr.right);
+                    if (validCidr.isIPv6())
+                        ipv6CidrGroupsTableBuilder.add(validCidr, cidrGroupName);
+                    else
+                        ipv4CidrGroupsTableBuilder.add(validCidr, cidrGroupName);
+                }
+                catch (RuntimeException e)
+                {
+                    // We should not have this scenario after introducing CIDR cql datatype which ensures
+                    // invalid CIDRs can't be inserted into the table using insert/update cql commands
+                    logger.error("Invalid CIDR {} found in the table, ignoring this entry", cidr);
+                }
+            }
+        }
+
+        ipv4CidrGroupsTable = ipv4CidrGroupsTableBuilder.build();
+        ipv6CidrGroupsTable = ipv6CidrGroupsTableBuilder.build();
+    }
+
+    public Set<String> lookupCidrGroupsCacheForIp(InetAddress ipAddr)
+    {
+        // Based on the incoming IP format, lookup in appropriate CIDR groups mapping
+        if (ipAddr instanceof Inet6Address)
+            return ipv6CidrGroupsTable.lookupLongestMatchForIP(ipAddr);
+        else
+            return ipv4CidrGroupsTable.lookupLongestMatchForIP(ipAddr);
+    }
+}
diff --git a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java
new file mode 100644
index 0000000000..6659bdcc15
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.cql3.statements.SelectStatement;
+import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.db.marshal.ByteBufferAccessor;
+import org.apache.cassandra.db.marshal.InetAddressType;
+import org.apache.cassandra.db.marshal.ShortType;
+import org.apache.cassandra.db.marshal.TupleType;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.transport.messages.ResultMessage;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.cassandra.utils.MBeanWrapper;
+import org.apache.cassandra.utils.Pair;
+
+import static org.apache.cassandra.service.QueryState.forInternalCalls;
+import static org.apache.cassandra.utils.Clock.Global.nanoTime;
+
+
+public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean
+{
+    public static final String MBEAN_NAME = "org.apache.cassandra.db:type=CIDRGroupsMappingManager";
+
+    private SelectStatement getCidrGroupsStatement;
+    private SelectStatement getCidrsForCidrGroupStatement;
+
+    private static final int PAGE_SIZE = 128;
+
+    public void setup()
+    {
+        if (!MBeanWrapper.instance.isRegistered(MBEAN_NAME))
+            MBeanWrapper.instance.registerMBean(this, MBEAN_NAME);
+
+        String getCidrGroupsQuery = String.format("SELECT %s FROM %s.%s",
+                                                  AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME,
+                                                  SchemaConstants.AUTH_KEYSPACE_NAME,
+                                                  AuthKeyspace.CIDR_GROUPS);
+        getCidrGroupsStatement = (SelectStatement) QueryProcessor.getStatement(getCidrGroupsQuery,
+                                                                               ClientState.forInternalCalls());
+
+        String getCidrsForCidrGroupQuery = String.format("SELECT %s FROM %s.%s where %s = ?",
+                                                         AuthKeyspace.CIDR_GROUPS_TBL_CIDRS_COL_NAME,
+                                                         SchemaConstants.AUTH_KEYSPACE_NAME,
+                                                         AuthKeyspace.CIDR_GROUPS,
+                                                         AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME);
+        getCidrsForCidrGroupStatement = (SelectStatement) QueryProcessor.getStatement(getCidrsForCidrGroupQuery,
+                                                                                      ClientState.forInternalCalls());
+    }
+
+    @VisibleForTesting
+    ResultMessage.Rows select(SelectStatement statement, QueryOptions options)
+    {
+        return statement.execute(forInternalCalls(), options, nanoTime());
+    }
+
+    UntypedResultSet process(String query, ConsistencyLevel cl) throws RequestExecutionException
+    {
+        return QueryProcessor.process(query, cl);
+    }
+
+    protected static String getCidrTuplesSetString(List<CIDR> cidrs)
+    {
+        String inner = cidrs.stream().map(CIDR::asCqlTupleString)
+                            .collect(Collectors.joining(", "));
+        return '{' + inner + '}';
+    }
+
+    public UntypedResultSet getCidrGroupsTableEntries()
+    {
+        String getAllRowsQuery = String.format("SELECT * FROM %s.%s",
+                                               SchemaConstants.AUTH_KEYSPACE_NAME,
+                                               AuthKeyspace.CIDR_GROUPS);
+
+        return QueryProcessor.executeInternalWithPaging(getAllRowsQuery, PAGE_SIZE);
+    }
+
+    public Set<String> getAvailableCidrGroups()
+    {
+        Set<String> availableCidrGroups = new HashSet<>();
+
+        QueryOptions options = QueryOptions.forInternalCalls(CassandraAuthorizer.authReadConsistencyLevel(),
+                                                             Collections.emptyList());
+
+        ResultMessage.Rows rows = select(getCidrGroupsStatement, options);
+        UntypedResultSet result = UntypedResultSet.create(rows.result);
+
+        for (UntypedResultSet.Row row : result)
+        {
+            if (!row.has(AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME))
+                throw new IllegalStateException("Invalid row " + row + " in table: " +
+                                                SchemaConstants.AUTH_KEYSPACE_NAME + '.' + AuthKeyspace.CIDR_GROUPS);
+
+            availableCidrGroups.add(row.getString(AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME));
+        }
+
+        return availableCidrGroups;
+    }
+
+    public Set<Pair<InetAddress, Short>> retrieveCidrsFromRow(UntypedResultSet.Row row)
+    {
+        if (!row.has(AuthKeyspace.CIDR_GROUPS_TBL_CIDRS_COL_NAME))
+            throw new RuntimeException("Invalid row, doesn't have column " +
+                                       AuthKeyspace.CIDR_GROUPS_TBL_CIDRS_COL_NAME);
+
+        Set<Pair<InetAddress, Short>> cidrs = new HashSet<>();
+        TupleType tupleType = new TupleType(Arrays.asList(InetAddressType.instance, ShortType.instance));
+
+        Set<ByteBuffer> cidrAsTuples = row.getFrozenSet(AuthKeyspace.CIDR_GROUPS_TBL_CIDRS_COL_NAME, tupleType);
+        for (ByteBuffer cidrAsTuple : cidrAsTuples)
+        {
+            ByteBuffer[] splits = tupleType.split(ByteBufferAccessor.instance, cidrAsTuple);
+            InetAddress ip = InetAddressType.instance.compose(splits[0]);
+            short netMask = ShortType.instance.compose(splits[1]);
+            cidrs.add(Pair.create(ip, netMask));
+        }
+
+        return cidrs;
+    }
+
+    public Set<String> getCidrsOfCidrGroupAsStrings(String cidrGroupName)
+    {
+        QueryOptions options = QueryOptions.forInternalCalls(CassandraAuthorizer.authReadConsistencyLevel(),
+                                                             Lists.newArrayList(ByteBufferUtil.bytes(cidrGroupName)));
+        ResultMessage.Rows rows = select(getCidrsForCidrGroupStatement, options);
+        UntypedResultSet result = UntypedResultSet.create(rows.result);
+
+        if (result.isEmpty())
+            return Collections.emptySet();
+
+        Set<Pair<InetAddress, Short>> allCidrs = retrieveCidrsFromRow(result.one());
+        Set<String> cidrStrs = new HashSet<>();
+        for (Pair<InetAddress, Short> cidr : allCidrs)
+            cidrStrs.add(cidr.left().getHostAddress() + '/' + cidr.right());
+
+        return cidrStrs;
+    }
+
+    public void updateCidrGroup(String cidrGroupName, List<String> cidrs)
+    {
+        List<CIDR> validCidrs = new ArrayList<>();
+        for (String cidr : cidrs)
+        {
+            validCidrs.add(CIDR.getInstance(cidr));
+        }
+
+        String query = String.format("UPDATE %s.%s SET %s = %s WHERE %s = '%s'",
+                                     SchemaConstants.AUTH_KEYSPACE_NAME,
+                                     AuthKeyspace.CIDR_GROUPS,
+                                     AuthKeyspace.CIDR_GROUPS_TBL_CIDRS_COL_NAME,
+                                     getCidrTuplesSetString(validCidrs),
+                                     AuthKeyspace.CIDR_GROUPS_TBL_CIDR_GROUP_COL_NAME,
+                                     cidrGroupName);
+
+        process(query, CassandraAuthorizer.authWriteConsistencyLevel());
+    }
+
+    @VisibleForTesting
+    void dropCidrGroupIfExists(String cidrGroupName)
+    {
+        String query = String.format("DELETE FROM %s.%s WHERE cidr_group = '%s'",
+                                     SchemaConstants.AUTH_KEYSPACE_NAME,
+                                     AuthKeyspace.CIDR_GROUPS,
+                                     cidrGroupName);
+
+        process(query, CassandraAuthorizer.authWriteConsistencyLevel());
+    }
+
+    public void dropCidrGroup(String cidrGroupName)
+    {
+        Set<String> cidrs = getCidrsOfCidrGroupAsStrings(cidrGroupName);
+        if (cidrs.isEmpty())
+            throw new RuntimeException("CIDR group '" + cidrGroupName + "' doesn't exists");
+
+        dropCidrGroupIfExists(cidrGroupName);
+    }
+
+    public void recreateCidrGroupsMapping(Map<String, List<String>> cidrGroupsMapping)
+    {
+        Set<String> existingMappings = getAvailableCidrGroups();
+
+        // Overwrites mappings of existing cidr groups and inserts new cidr groups
+        for (Map.Entry<String, List<String>> cidrGroupMapping : cidrGroupsMapping.entrySet())
+        {
+            String cidrGroupName = cidrGroupMapping.getKey();
+            updateCidrGroup(cidrGroupName, cidrGroupMapping.getValue());
+            existingMappings.remove(cidrGroupName);
+        }
+
+        // Delete old CIDR groups which do not exist in new mappings
+        for (String cidrGroupName : existingMappings)
+        {
+            dropCidrGroupIfExists(cidrGroupName);
+        }
+    }
+
+    public Set<String> getCidrGroupsOfIP(String ipStr)
+    {
+        try
+        {
+            return DatabaseDescriptor.getCIDRAuthorizer().lookupCidrGroupsForIp(InetAddress.getByName(ipStr));
+        }
+        catch (UnknownHostException e)
+        {
+            throw new IllegalArgumentException("Invalid IP " + ipStr, e);
+        }
+    }
+
+    public void loadCidrGroupsCache()
+    {
+        DatabaseDescriptor.getCIDRAuthorizer().loadCidrGroupsCache();
+    }
+}
diff --git a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManagerMBean.java b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManagerMBean.java
new file mode 100644
index 0000000000..3e8223936e
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManagerMBean.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class provides utility functions to list/update/drop CIDR groups mappings.
+ * nodetool commands invoke these functions, hence need to register mbean even when
+ * CIDR authorization is disabled
+ */
+public interface CIDRGroupsMappingManagerMBean
+{
+    /**
+     * Retrieves available CIDR groups in the table {@link AuthKeyspace#CIDR_GROUPS}
+     * @return returns CIDR groups set
+     */
+    public Set<String> getAvailableCidrGroups();
+
+    /**
+     * Retrieves all CIDRs associated with a given CIDR group in the table {@link AuthKeyspace#CIDR_GROUPS},
+     * including invalid CIDRs (if exists)
+     * @param cidrGroupName CIDR group to retrieve associated CIDRs
+     * @return returns set of CIDRs as strings
+     */
+    public Set<String> getCidrsOfCidrGroupAsStrings(String cidrGroupName);
+
+    /**
+     * Insert/Update CIDR group to CIDRs mapping in the table {@link AuthKeyspace#CIDR_GROUPS}
+     * @param cidrGroupName CIDR group name
+     * @param cidrs List of CIDRs as strings
+     */
+    public void updateCidrGroup(String cidrGroupName, List<String> cidrs);
+
+    /**
+     * Delete a CIDR group and associated mapping from the table {@link AuthKeyspace#CIDR_GROUPS}
+     * throws exception if input cidr group name doesn't exist in the table before deletion
+     * @param cidrGroupName CIDR group name
+     */
+    public void dropCidrGroup(String cidrGroupName);
+
+    /**
+     * Update {@link AuthKeyspace#CIDR_GROUPS} table with completly new mappings. Insert nonexisting CIDR groups,
+     * update existing CIDR groups with given set of CIDRs and delete nolonger valid CIDR groups.
+     * @param cidrGroupsMapping CIDR group mappings
+     */
+    public void recreateCidrGroupsMapping(Map<String, List<String>> cidrGroupsMapping);
+
+    /**
+     * Get CIDR groups of a given IP, when CIDR authorizer is enabled.
+     * Throws exception if CIDR authorizer is disabled
+     * @param ipStr IP as a string
+     * @return Set of strings
+     */
+    public Set<String> getCidrGroupsOfIP(String ipStr);
+
+    /**
+     * Trigger reload of CIDR groups cache
+     */
+    public void loadCidrGroupsCache();
+}
diff --git a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingTable.java b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingTable.java
new file mode 100644
index 0000000000..8ebd21ff7a
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingTable.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetAddress;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.cassandra.cql3.CIDR;
+
+/**
+ * This interface defines functionality to be provided by algorithm(s) implementing CIDR to CIDR groups mappings,
+ * to facilitate the efficient way to find the longest matching CIDR for a given IP
+ */
+public interface CIDRGroupsMappingTable<V>
+{
+    default String getIPTypeString(boolean isIPv6)
+    {
+        return isIPv6 ? "IPv6" : "IPv4";
+    }
+
+    static <V> Builder<V> builder(boolean isIPv6)
+    {
+        return new Builder<>(isIPv6);
+    }
+
+    /**
+     * Finds the longest matching CIDR for the given IP
+     * @param ip IP to lookup CIDR group
+     * @return returns CIDR group(s) associated with the longest matching CIDR for the given IP, returns null if no match found
+     */
+    public Set<V> lookupLongestMatchForIP(InetAddress ip);
+
+
+    /**
+     * Builder to add CIDR to CIDR groups mappings and construct a mapping table with them
+     */
+    class Builder<V>
+    {
+        private final Map<CIDR, Set<V>> cidrMappings = new HashMap<>();
+        private final boolean isIPv6;
+
+        private Builder(boolean isIPv6)
+        {
+            this.isIPv6 = isIPv6;
+        }
+
+        public void add(CIDR cidr, V cidrGroup)
+        {
+            cidrMappings.computeIfAbsent(cidr, k -> new HashSet<>()).add(cidrGroup);
+        }
+
+        /**
+         * Provides an instance of algorithm implementing CIDR to CIDR groups mappings
+         * @return returns instance of CIDRGroupsMappingTable
+         */
+        public CIDRGroupsMappingTable<V> build()
+        {
+            return new CIDRGroupsMappingIntervalTree<>(isIPv6, cidrMappings);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/auth/CIDRPermissions.java b/src/java/org/apache/cassandra/auth/CIDRPermissions.java
new file mode 100644
index 0000000000..c4870f2a90
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/CIDRPermissions.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+
+/**
+ * Contains CIDR permissions of a role
+ */
+public abstract class CIDRPermissions
+{
+    /**
+     * Determines whether this permissions object allows access from given cidr group(s)
+     * @param cidrGroup set of CIDR groups
+     * @return returns true if role has access, otherwise false
+     */
+    public abstract boolean canAccessFrom(Set<String> cidrGroup);
+
+    /**
+     * Determines whether this permissions object restricts access from some CIDR groups
+     * or allows access from any CIDR group
+     * @return true if restricts access, otherwise false
+     */
+    public abstract boolean restrictsAccess();
+
+    /**
+     * Returns CIDR permissions allowed by this object
+     * @return returns set of CIDR groups
+     */
+    public abstract Set<String> allowedCIDRGroups();
+
+    /**
+     *  Validates does this object contains valid CIDR groups
+     */
+    public abstract void validate();
+
+    // This class represents a subset of CIDR permissions, i.e, not ALL and not NONE.
+    private static class SubsetPermissions extends CIDRPermissions
+    {
+        private final Set<String> subset;
+
+        public SubsetPermissions(Set<String> subset)
+        {
+            Preconditions.checkNotNull(subset);
+            this.subset = subset;
+        }
+
+        public boolean canAccessFrom(Set<String> cidrGroups)
+        {
+            return subset.stream().anyMatch(cidrGroups::contains);
+        }
+
+        public boolean restrictsAccess()
+        {
+            return true;
+        }
+
+        public Set<String> allowedCIDRGroups()
+        {
+            return ImmutableSet.copyOf(subset);
+        }
+
+        public boolean equals(Object o)
+        {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            SubsetPermissions that = (SubsetPermissions) o;
+
+            return subset.equals(that.subset);
+        }
+
+        public int hashCode()
+        {
+            return subset.hashCode();
+        }
+
+        public String toString()
+        {
+            StringJoiner joiner = new StringJoiner(", ");
+            subset.forEach(joiner::add);
+            return joiner.toString();
+        }
+
+        public void validate()
+        {
+            Set<String> availableCidrGroups = DatabaseDescriptor.getCIDRAuthorizer()
+                                                                .getCidrGroupsMappingManager()
+                                                                .getAvailableCidrGroups();
+            Set<String> unknownCidrGroups = Sets.difference(subset, availableCidrGroups);
+            if (!unknownCidrGroups.isEmpty())
+            {
+                throw new InvalidRequestException("Invalid CIDR group(s): " + subset + ". Available CIDR Groups are: "
+                                                  + availableCidrGroups);
+            }
+        }
+    }
+
+    private static final CIDRPermissions ALL = new CIDRPermissions()
+    {
+        public boolean canAccessFrom(Set<String> cidrGroup)
+        {
+            return true;
+        }
+
+        public boolean restrictsAccess()
+        {
+            return false;
+        }
+
+        public Set<String> allowedCIDRGroups()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        public String toString()
+        {
+            return "ALL";
+        }
+
+        public void validate()
+        {
+
+        }
+    };
+
+    private static final CIDRPermissions NONE = new CIDRPermissions()
+    {
+        public boolean canAccessFrom(Set<String> cidrGroup)
+        {
+            return false;
+        }
+
+        public boolean restrictsAccess()
+        {
+            return true;
+        }
+
+        public Set<String> allowedCIDRGroups()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        public String toString()
+        {
+            return "n/a";
+        }
+
+        public void validate()
+        {
+            throw new UnsupportedOperationException();
+        }
+    };
+
+    /**
+     * Generates CIDR permissions object which allows from all CIDR groups
+     * @return returns CIDRPermissions object
+     */
+    public static CIDRPermissions all()
+    {
+        return ALL;
+    }
+
+    /**
+     * Generates CIDR permissions object which doesn't allow from any CIDR group
+     * @return returns CIDRPermissions object
+     */
+    public static CIDRPermissions none()
+    {
+        return NONE;
+    }
+
+    /**
+     * Generates CIDR permissions object with given CIDR groups
+     * @param cidrGroups set of CIDR groups
+     * @return returns CIDRPermissions object
+     */
+    public static CIDRPermissions subset(Set<String> cidrGroups)
+    {
+        return new SubsetPermissions(cidrGroups);
+    }
+
+    /**
+     * Builder to generate CIDR Permissions
+     */
+    public static class Builder
+    {
+        private final Set<String> cidrGroups = new HashSet<>();
+        private boolean isAll = false;
+        private boolean modified = false;
+
+        public void add(String cidrGroup)
+        {
+            Preconditions.checkArgument(!isAll, "All has been set");
+            cidrGroups.add(cidrGroup);
+            modified = true;
+        }
+
+        public void all()
+        {
+            Preconditions.checkArgument(cidrGroups.isEmpty(), "CIDR Groups have already been set");
+            isAll = true;
+            modified = true;
+        }
+
+        public boolean isModified()
+        {
+            return modified;
+        }
+
+        public CIDRPermissions build()
+        {
+            if (cidrGroups.isEmpty())
+            {
+                return CIDRPermissions.all();
+            }
+            else
+            {
+                return subset(cidrGroups);
+            }
+        }
+    }
+
+    /**
+     * Builder object to generate CIDR permissions
+     * @return returns Builder object
+     */
+    public static Builder builder()
+    {
+        return new Builder();
+    }
+}
diff --git a/src/java/org/apache/cassandra/auth/CIDRPermissionsCache.java b/src/java/org/apache/cassandra/auth/CIDRPermissionsCache.java
new file mode 100644
index 0000000000..38c3a5d13f
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/CIDRPermissionsCache.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.util.Map;
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.apache.cassandra.config.DatabaseDescriptor;
+
+public class CIDRPermissionsCache extends AuthCache<RoleResource, CIDRPermissions>
+{
+    public static final String CACHE_NAME = "CidrPermissionsCache";
+
+    public CIDRPermissionsCache(Function<RoleResource, CIDRPermissions> loadFunction,
+                                Supplier<Map<RoleResource, CIDRPermissions>> bulkLoadFunction,
+                                BooleanSupplier cacheEnabledDelegate)
+    {
+        super(CACHE_NAME,
+              DatabaseDescriptor::setRolesValidity,
+              DatabaseDescriptor::getRolesValidity,
+              DatabaseDescriptor::setRolesUpdateInterval,
+              DatabaseDescriptor::getRolesUpdateInterval,
+              DatabaseDescriptor::setRolesCacheMaxEntries,
+              DatabaseDescriptor::getRolesCacheMaxEntries,
+              DatabaseDescriptor::setRolesCacheActiveUpdate,
+              DatabaseDescriptor::getRolesCacheActiveUpdate,
+              loadFunction,
+              bulkLoadFunction,
+              cacheEnabledDelegate);
+    }
+
+    /**
+     * Invalidate a role from CIDR permissions cache
+     * @param roleName role for which to invalidate the cache
+     * @return boolean returns true if given role found in the cache and invalidated, otherwise returns false
+     */
+    public boolean invalidateCidrPermissions(String roleName)
+    {
+        if (cache.getIfPresent(roleName) == null)
+            return false;
+
+        invalidate(RoleResource.role(roleName));
+        return true;
+    }
+
+    @Override
+    protected void unregisterMBean()
+    {
+        super.unregisterMBean();
+    }
+}
diff --git a/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java b/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java
new file mode 100644
index 0000000000..459d845bee
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.cql3.statements.SelectStatement;
+import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.service.reads.range.RangeCommands;
+import org.apache.cassandra.transport.messages.ResultMessage;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.cassandra.utils.MBeanWrapper;
+
+import static org.apache.cassandra.service.QueryState.forInternalCalls;
+import static org.apache.cassandra.utils.Clock.Global.nanoTime;
+
+/**
+ * Provides functionality to list/update/drop CIDR permissions of a role
+ * Backend to build Role to CIDR permissions cache
+ */
+public class CIDRPermissionsManager implements CIDRPermissionsManagerMBean, AuthCache.BulkLoader<RoleResource, CIDRPermissions>
+{
+    public static final String MBEAN_NAME = "org.apache.cassandra.db:type=CIDRPermissionsManager";
+
+    private static final Logger logger = LoggerFactory.getLogger(CIDRPermissionsManager.class);
+    private SelectStatement getCidrPermissionsOfUserStatement = null;
+
+    public void setup()
+    {
+        if (!MBeanWrapper.instance.isRegistered(MBEAN_NAME))
+            MBeanWrapper.instance.registerMBean(this, MBEAN_NAME);
+
+        String getCidrPermissionsOfUserQuery = String.format("SELECT %s FROM %s.%s WHERE %s = ?",
+                                                             AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME,
+                                                             SchemaConstants.AUTH_KEYSPACE_NAME,
+                                                             AuthKeyspace.CIDR_PERMISSIONS,
+                                                             AuthKeyspace.CIDR_PERMISSIONS_TBL_ROLE_COL_NAME);
+        getCidrPermissionsOfUserStatement = (SelectStatement) QueryProcessor.getStatement(getCidrPermissionsOfUserQuery,
+                                                                                          ClientState.forInternalCalls());
+    }
+
+    @VisibleForTesting
+    ResultMessage.Rows select(SelectStatement statement, QueryOptions options)
+    {
+        return statement.execute(forInternalCalls(), options, nanoTime());
+    }
+
+    @VisibleForTesting
+    UntypedResultSet process(String query, ConsistencyLevel cl) throws RequestExecutionException
+    {
+        return QueryProcessor.process(query, cl);
+    }
+
+    private Set<String> getAuthorizedCIDRGroups(String name)
+    {
+        QueryOptions options = QueryOptions.forInternalCalls(CassandraAuthorizer.authReadConsistencyLevel(),
+                                                             Lists.newArrayList(ByteBufferUtil.bytes(name)));
+
+        ResultMessage.Rows rows = select(getCidrPermissionsOfUserStatement, options);
+        UntypedResultSet result = UntypedResultSet.create(rows.result);
+        if (!result.isEmpty() && result.one().has(AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME))
+        {
+            return result.one().getFrozenSet(AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME, UTF8Type.instance);
+        }
+
+        return Collections.emptySet();
+    }
+
+    private static String getCidrPermissionsSetString(CIDRPermissions permissions)
+    {
+        String inner = "";
+
+        if (permissions.restrictsAccess())
+            inner = permissions.allowedCIDRGroups().stream().map(s -> '\'' + s + '\'')
+                               .collect(Collectors.joining(", "));
+
+        return '{' + inner + '}';
+    }
+
+    /**
+     * Get CIDR permissions of a role
+     * @param role role for which to get CIDR permissions
+     * @return returns CIDR permissions of a role
+     */
+    public CIDRPermissions getCidrPermissionsForRole(RoleResource role)
+    {
+        if (!Roles.canLogin(role))
+        {
+            return CIDRPermissions.none();
+        }
+        if (Roles.hasSuperuserStatus(role) && !DatabaseDescriptor.getCidrChecksForSuperusers())
+        {
+            return CIDRPermissions.all();
+        }
+
+        Set<String> cidrGroups = getAuthorizedCIDRGroups(role.getRoleName());
+        // User don't have CIDR permissions explicitly enabled, i.e, allow from all
+        if (cidrGroups == null || cidrGroups.isEmpty())
+        {
+            // No explicit CIDR groups set for the role, allow from all
+            return CIDRPermissions.all();
+        }
+
+        return CIDRPermissions.subset(cidrGroups);
+    }
+
+    /**
+     * Set CIDR permissions for a given role
+     * @param role role for which to set CIDR permissions
+     * @param cidrPermissions CIR permissions to set for the role
+     */
+    public void setCidrGroupsForRole(RoleResource role, CIDRPermissions cidrPermissions)
+    {
+        String query = String.format("UPDATE %s.%s SET %s = %s WHERE %s = '%s'",
+                                     SchemaConstants.AUTH_KEYSPACE_NAME,
+                                     AuthKeyspace.CIDR_PERMISSIONS,
+                                     AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME,
+                                     getCidrPermissionsSetString(cidrPermissions),
+                                     AuthKeyspace.CIDR_PERMISSIONS_TBL_ROLE_COL_NAME,
+                                     role.getRoleName());
+
+        process(query, CassandraAuthorizer.authWriteConsistencyLevel());
+    }
+
+    /**
+     * Drop CIDR permissions of a role, i.e, delete corresponding row from the table
+     * @param role for which to drop cidr permissions
+     */
+    public void drop(RoleResource role)
+    {
+        String query = String.format("DELETE FROM %s.%s WHERE role = '%s'",
+                                     SchemaConstants.AUTH_KEYSPACE_NAME,
+                                     AuthKeyspace.CIDR_PERMISSIONS,
+                                     role.getRoleName());
+
+        process(query, CassandraAuthorizer.authWriteConsistencyLevel());
+    }
+
+    /**
+     * Function to bulk load role to cidr permissions cache
+     * This gets called when cache warmup is enabled in the config
+     * @return returns nothing
+     */
+    @Override
+    public Supplier<Map<RoleResource, CIDRPermissions>> bulkLoader()
+    {
+        return () -> {
+            if (!RangeCommands.sufficientLiveNodesForSelectStar(AuthKeyspace.metadata().tables.getNullable(AuthKeyspace.CIDR_PERMISSIONS),
+                                                                AuthProperties.instance.getReadConsistencyLevel()))
+            {
+                // Prevent running the query we know will fail so as not to increment unavailable stats for a performance
+                // optimization
+                throw new RuntimeException("insufficient live nodes for " +
+                                           AuthProperties.instance.getReadConsistencyLevel() +
+                                           "pre-warm query again system_auth." + AuthKeyspace.CIDR_PERMISSIONS);
+            }
+
+            logger.info("Pre-warming CIDR permissions cache from cidr_permissions table");
+            Map<RoleResource, CIDRPermissions> entries = new HashMap<>();
+            UntypedResultSet rows = process(String.format("SELECT %s, %s FROM %s.%s",
+                                                          AuthKeyspace.CIDR_PERMISSIONS_TBL_ROLE_COL_NAME,
+                                                          AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME,
+                                                          SchemaConstants.AUTH_KEYSPACE_NAME,
+                                                          AuthKeyspace.CIDR_PERMISSIONS),
+                                            CassandraAuthorizer.authReadConsistencyLevel());
+
+            for (UntypedResultSet.Row row : rows)
+            {
+                RoleResource role = RoleResource.role(row.getString(AuthKeyspace.CIDR_PERMISSIONS_TBL_ROLE_COL_NAME));
+                CIDRPermissions.Builder builder = new CIDRPermissions.Builder();
+                Set<String> cidrGroups = row.getFrozenSet(AuthKeyspace.CIDR_PERMISSIONS_TBL_CIDR_GROUPS_COL_NAME,
+                                                          UTF8Type.instance);
+                for (String cidrGroup : cidrGroups)
+                    builder.add(cidrGroup);
+                entries.put(role, builder.build());
+            }
+
+            return entries;
+        };
+    }
+
+    public boolean invalidateCidrPermissionsCache(String roleName)
+    {
+        return DatabaseDescriptor.getCIDRAuthorizer().invalidateCidrPermissionsCache(roleName);
+    }
+}
diff --git a/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java b/src/java/org/apache/cassandra/auth/CIDRPermissionsManagerMBean.java
similarity index 50%
copy from test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java
copy to src/java/org/apache/cassandra/auth/CIDRPermissionsManagerMBean.java
index 51c61bbb12..c5376300c6 100644
--- a/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java
+++ b/src/java/org/apache/cassandra/auth/CIDRPermissionsManagerMBean.java
@@ -16,32 +16,17 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.cql3.statements;
+package org.apache.cassandra.auth;
 
-import org.junit.Assert;
-import org.junit.Test;
-
-import org.apache.cassandra.auth.DCPermissions;
-import org.apache.cassandra.cql3.CQLStatement;
-import org.apache.cassandra.cql3.QueryProcessor;
-
-public class CreateUserStatementTest
+/**
+ * JMX functions related to CIDR permissions cache and table
+ */
+public interface CIDRPermissionsManagerMBean
 {
-    private static CreateRoleStatement parse(String query)
-    {
-        CQLStatement.Raw stmt = QueryProcessor.parseStatement(query);
-        Assert.assertTrue(stmt instanceof CreateRoleStatement);
-        return (CreateRoleStatement) stmt;
-    }
-
-    private static DCPermissions dcPerms(String query)
-    {
-        return parse(query).dcPermissions;
-    }
-
-    @Test
-    public void allDcsImplicit() throws Exception
-    {
-        Assert.assertFalse(dcPerms("CREATE USER u1").restrictsAccess());
-    }
+    /**
+     * Invalidate given role from CIDR permissions cache
+     * @param roleName role to invalidate
+     * @return returns true if given role found in the cache and invalidated, false otherwise
+     */
+    public boolean invalidateCidrPermissionsCache(String roleName);
 }
diff --git a/src/java/org/apache/cassandra/auth/CassandraCIDRAuthorizer.java b/src/java/org/apache/cassandra/auth/CassandraCIDRAuthorizer.java
new file mode 100644
index 0000000000..ffbfdcadba
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/CassandraCIDRAuthorizer.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetAddress;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.utils.MonotonicClock;
+import org.apache.cassandra.utils.NoSpamLogger;
+
+/**
+ * CassandraCIDRAuthorizer is backend for CIDR authorization checks
+ * 1, Provides functionality to populate Role to CIDR permisssions cache and
+ * 2, Uses CIDR groups mapping cache as backend to lookup CIDR groups of an IP
+ */
+public class CassandraCIDRAuthorizer extends AbstractCIDRAuthorizer
+{
+    private static final Logger logger = LoggerFactory.getLogger(AuthenticatedUser.class);
+    private static final NoSpamLogger noSpamLogger = NoSpamLogger.getLogger(logger, 1, TimeUnit.MINUTES);
+
+    protected static CIDRPermissionsCache cidrPermissionsCache;
+    protected static CIDRGroupsMappingCache cidrGroupsMappingCache;
+
+    @Override
+    public void setup()
+    {
+        commonSetup();
+
+        // Create <Role to CIDR permissions> cache
+        cidrPermissionsCache = new CIDRPermissionsCache(this::getCidrPermissionsForRole,
+                                                        this.bulkLoadCidrPermsCache(),
+                                                        this::requireAuthorization);
+
+        // Create CIDR groups cache
+        cidrGroupsMappingCache = new CIDRGroupsMappingCache(cidrGroupsMappingManager, cidrAuthorizerMetrics);
+    }
+
+    @Override
+    public void initCaches()
+    {
+        AuthCacheService.instance.register(cidrPermissionsCache);
+
+        // Load CIDR groups cache during the startup, to avoid increased latency during the first CIDR check
+        loadCidrGroupsCache();
+    }
+
+    private CIDRPermissions getCidrPermissionsForRole(RoleResource role)
+    {
+        return cidrPermissionsManager.getCidrPermissionsForRole(role);
+    }
+
+    private Supplier<Map<RoleResource, CIDRPermissions>> bulkLoadCidrPermsCache()
+    {
+        return cidrPermissionsManager.bulkLoader();
+    }
+
+    @Override
+    public boolean invalidateCidrPermissionsCache(String roleName)
+    {
+        if (roleName == null || roleName.isEmpty())
+        {
+            cidrPermissionsCache.invalidate();
+            return true;
+        }
+
+        return cidrPermissionsCache.invalidateCidrPermissions(roleName);
+    }
+
+    @Override
+    public void loadCidrGroupsCache()
+    {
+        cidrGroupsMappingCache.loadCidrGroupsCache();
+    }
+
+    @Override
+    public Set<String> lookupCidrGroupsForIp(InetAddress ip)
+    {
+        return cidrGroupsMappingCache.lookupCidrGroupsForIp(ip);
+    }
+
+    @VisibleForTesting
+    protected boolean isMonitorMode()
+    {
+        return DatabaseDescriptor.getCidrAuthorizerMode() == CIDRAuthorizerMode.MONITOR;
+    }
+
+    private boolean hasCidrAccess(RoleResource role, InetAddress ipAddress)
+    {
+        CIDRPermissions cidrPermissions = cidrPermissionsCache.get(role);
+        // Superusers and Roles without CIDR restrictions, should be able to access even when
+        // CIDR authorization is enabled and CIDR groups mapping table is not populated yet
+        if (!cidrPermissions.restrictsAccess() && !isMonitorMode())
+            return true;
+
+        Set<String> cidrGroups = lookupCidrGroupsForIp(ipAddress);
+
+        if (isMonitorMode())
+        {
+            if (cidrGroups != null && !cidrPermissions.canAccessFrom(cidrGroups))
+                noSpamLogger.warn("Role {} accessed from unauthorized IP {}, CIDR group {}", role.getRoleName(),
+                                  ipAddress.getHostAddress(), cidrGroups);
+            else
+                noSpamLogger.info("Role {} accessed from IP {}, CIDR group {}", role.getRoleName(),
+                                  ipAddress.getHostAddress(), cidrGroups);
+
+            cidrAuthorizerMetrics.incrAcceptedAccessCount(cidrGroups);
+            return true;
+        }
+
+        // Reach here only for enforce mode
+        if (cidrGroups == null || cidrGroups.isEmpty() ||  // No CIDR group found for this IP
+            !cidrPermissions.canAccessFrom(cidrGroups))
+        {
+            cidrAuthorizerMetrics.incrRejectedAccessCount(cidrGroups);
+            return false;
+        }
+
+        cidrAuthorizerMetrics.incrAcceptedAccessCount(cidrGroups);
+        return true;
+    }
+
+    @Override
+    public boolean hasAccessFromIp(RoleResource role, InetAddress ipAddress)
+    {
+        long startTimeNanos = MonotonicClock.Global.approxTime.now();
+
+        boolean hasAccess = hasCidrAccess(role, ipAddress);
+
+        cidrAuthorizerMetrics.cidrChecksLatency.update(MonotonicClock.Global.approxTime.now() - startTimeNanos,
+                                                          TimeUnit.NANOSECONDS);
+        return hasAccess;
+    }
+}
diff --git a/src/java/org/apache/cassandra/auth/ICIDRAuthorizer.java b/src/java/org/apache/cassandra/auth/ICIDRAuthorizer.java
new file mode 100644
index 0000000000..737bbb220e
--- /dev/null
+++ b/src/java/org/apache/cassandra/auth/ICIDRAuthorizer.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetAddress;
+import java.util.Set;
+
+import org.apache.cassandra.config.ParameterizedClass;
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.metrics.CIDRAuthorizerMetrics;
+import org.apache.cassandra.utils.FBUtilities;
+
+/**
+ * Backend for CIDR authorization feature
+ */
+public interface ICIDRAuthorizer
+{
+    /**
+     * Supported modes by CIDR authorizer
+     */
+    public enum CIDRAuthorizerMode
+    {
+        MONITOR,
+        ENFORCE
+    }
+
+    public static ICIDRAuthorizer newCIDRAuthorizer(ParameterizedClass cidrAuthorizer)
+    {
+        if (cidrAuthorizer == null || cidrAuthorizer.class_name == null)
+        {
+            return new AllowAllCIDRAuthorizer();
+        }
+
+        String className = cidrAuthorizer.class_name;
+        if (!className.contains("."))
+        {
+            className = "org.apache.cassandra.auth." + className;
+        }
+        return FBUtilities.construct(className, "cidr authorizer");
+    }
+
+    public void setup();
+
+    /**
+     * Init caches held by CIDR authorizer
+     */
+    public void initCaches();
+
+    public CIDRGroupsMappingManager getCidrGroupsMappingManager();
+
+    public CIDRAuthorizerMetrics getCidrAuthorizerMetrics();
+
+    public boolean requireAuthorization();
+
+    /**
+     * Set CIDR permissions for a given role
+     * @param role role for which to set CIDR permissions
+     * @param cidrPermissions CIR permissions to set for the role
+     */
+    public void setCidrGroupsForRole(RoleResource role, CIDRPermissions cidrPermissions);
+
+    /**
+     * Drop CIDR permissions of a role
+     * @param role for which to drop cidr permissions
+     */
+    public void dropCidrPermissionsForRole(RoleResource role);
+
+    /**
+     * Invalidate given role from CIDR permissions cache
+     * @param roleName role to invalidate
+     * @return returns true if given role found in the cache and invalidated, false otherwise
+     */
+    public boolean invalidateCidrPermissionsCache(String roleName);
+
+    public void validateConfiguration() throws ConfigurationException;
+
+    /**
+     * Load CIDR groups mapping cache
+     */
+    public void loadCidrGroupsCache();
+
+    /**
+     * Lookup IP in CIDR groups mapping cache
+     * @param ip input IP to lookup CIDR group
+     * @return returns best matching CIDR group for this IP
+     */
+    public Set<String> lookupCidrGroupsForIp(InetAddress ip);
+
+    /**
+     * Determines does the given role has access from CIDR groups associated with given IP
+     * @param role role to check access
+     * @param ipAddress IP of the client
+     * @return returns true if role has access from given IP, false otherwise
+     */
+    public boolean hasAccessFromIp(RoleResource role, InetAddress ipAddress);
+}
diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java
index 5e904bbf3e..34a173c8c6 100644
--- a/src/java/org/apache/cassandra/config/Config.java
+++ b/src/java/org/apache/cassandra/config/Config.java
@@ -82,6 +82,8 @@ public class Config
     public String role_manager;
     public ParameterizedClass crypto_provider;
     public String network_authorizer;
+    public ParameterizedClass cidr_authorizer;
+
     @Replaces(oldName = "permissions_validity_in_ms", converter = Converters.MILLIS_DURATION_INT, deprecated = true)
     public volatile DurationSpec.IntMillisecondsBound permissions_validity = new DurationSpec.IntMillisecondsBound("2s");
     public volatile int permissions_cache_max_entries = 1000;
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 88c0ab9e7d..91a55aac7d 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -68,6 +68,7 @@ import org.apache.cassandra.auth.AllowAllInternodeAuthenticator;
 import org.apache.cassandra.auth.AuthConfig;
 import org.apache.cassandra.auth.IAuthenticator;
 import org.apache.cassandra.auth.IAuthorizer;
+import org.apache.cassandra.auth.ICIDRAuthorizer;
 import org.apache.cassandra.auth.IInternodeAuthenticator;
 import org.apache.cassandra.auth.INetworkAuthorizer;
 import org.apache.cassandra.auth.IRoleManager;
@@ -181,6 +182,8 @@ public class DatabaseDescriptor
     private static IAuthenticator authenticator;
     private static IAuthorizer authorizer;
     private static INetworkAuthorizer networkAuthorizer;
+    private static ICIDRAuthorizer cidrAuthorizer;
+
     // Don't initialize the role manager until applying config. The options supported by CassandraRoleManager
     // depend on the configured IAuthenticator, so defer creating it until that's been set.
     private static IRoleManager roleManager;
@@ -1584,6 +1587,72 @@ public class DatabaseDescriptor
         DatabaseDescriptor.networkAuthorizer = networkAuthorizer;
     }
 
+    public static ICIDRAuthorizer getCIDRAuthorizer()
+    {
+        return cidrAuthorizer;
+    }
+
+    public static void setCIDRAuthorizer(ICIDRAuthorizer cidrAuthorizer)
+    {
+        DatabaseDescriptor.cidrAuthorizer = cidrAuthorizer;
+    }
+
+    public static boolean getCidrChecksForSuperusers()
+    {
+        boolean defaultCidrChecksForSuperusers = false;
+
+        if (conf.cidr_authorizer == null || conf.cidr_authorizer.parameters == null)
+            return defaultCidrChecksForSuperusers;
+
+        String value = conf.cidr_authorizer.parameters.get("cidr_checks_for_superusers");
+        if (value == null || value.isEmpty())
+            return defaultCidrChecksForSuperusers;
+
+        return Boolean.parseBoolean(value);
+    }
+
+    public static ICIDRAuthorizer.CIDRAuthorizerMode getCidrAuthorizerMode()
+    {
+        ICIDRAuthorizer.CIDRAuthorizerMode defaultCidrAuthorizerMode = ICIDRAuthorizer.CIDRAuthorizerMode.MONITOR;
+
+        if (conf.cidr_authorizer == null || conf.cidr_authorizer.parameters == null)
+            return defaultCidrAuthorizerMode;
+
+        String cidrAuthorizerMode = conf.cidr_authorizer.parameters.get("cidr_authorizer_mode");
+        if (cidrAuthorizerMode == null || cidrAuthorizerMode.isEmpty())
+            return defaultCidrAuthorizerMode;
+
+        return ICIDRAuthorizer.CIDRAuthorizerMode.valueOf(cidrAuthorizerMode.toUpperCase());
+    }
+
+    public static int getCidrGroupsCacheRefreshInterval()
+    {
+        int defaultCidrGroupsCacheRefreshInterval = 5; // mins
+
+        if (conf.cidr_authorizer == null  || conf.cidr_authorizer.parameters == null)
+            return defaultCidrGroupsCacheRefreshInterval;
+
+        String cidrGroupsCacheRefreshInterval = conf.cidr_authorizer.parameters.get("cidr_groups_cache_refresh_interval");
+        if (cidrGroupsCacheRefreshInterval == null || cidrGroupsCacheRefreshInterval.isEmpty())
+            return defaultCidrGroupsCacheRefreshInterval;
+
+        return Integer.parseInt(cidrGroupsCacheRefreshInterval);
+    }
+
+    public static int getIpCacheMaxSize()
+    {
+        int defaultIpCacheMaxSize = 100;
+
+        if (conf.cidr_authorizer == null  || conf.cidr_authorizer.parameters == null)
+            return defaultIpCacheMaxSize;
+
+        String ipCacheMaxSize = conf.cidr_authorizer.parameters.get("ip_cache_max_size");
+        if (ipCacheMaxSize == null || ipCacheMaxSize.isEmpty())
+            return defaultIpCacheMaxSize;
+
+        return Integer.parseInt(ipCacheMaxSize);
+    }
+
     public static void setAuthFromRoot(boolean fromRoot)
     {
         conf.traverse_auth_from_root = fromRoot;
diff --git a/src/java/org/apache/cassandra/cql3/CIDR.java b/src/java/org/apache/cassandra/cql3/CIDR.java
new file mode 100644
index 0000000000..7da0981535
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/CIDR.java
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+import org.apache.cassandra.utils.Pair;
+
+/**
+ * Contains a CIDR, and operations on it
+ */
+public final class CIDR
+{
+    private final InetAddress startIpAddress;
+    private final InetAddress endIpAddress;
+    // max mask value with IPv6 is 128, not easy to handle value 128 using Byte, hence using short
+    private final short netMask;
+
+    /**
+     * Generates a CIDR from given IP and netmask
+     * @param ipAddress IP address of CIDR
+     * @param netMask   netmask of CIDR
+     */
+    public CIDR(InetAddress ipAddress, short netMask)
+    {
+        if (netMask > maxNetMaskAllowed(ipAddress))
+            throw new IllegalArgumentException("Invalid netmask " + netMask + " for IP " + ipAddress.getHostAddress());
+
+        Pair<InetAddress, InetAddress> ipRange = calcIpRangeOfCidr(ipAddress, netMask);
+        this.startIpAddress = ipRange.left();
+        this.endIpAddress = ipRange.right();
+        this.netMask = netMask;
+    }
+
+    /**
+     * Generates a CIDR from given string
+     * @param cidrStr CIDR as string
+     */
+    public static CIDR getInstance(String cidrStr)
+    {
+        if (cidrStr == null || cidrStr.isEmpty())
+        {
+            throw new IllegalArgumentException(String.format("%s is not a valid CIDR String", cidrStr));
+        }
+
+        String[] parts = cidrStr.split("/");
+        if (parts.length != 2)
+        {
+            throw new IllegalArgumentException(String.format("%s is not a valid CIDR String", cidrStr));
+        }
+
+        short netMask = Short.parseShort(parts[1]);
+
+        InetAddress ipAddress;
+        try
+        {
+            ipAddress = InetAddress.getByName(parts[0]);
+            if (ipAddress instanceof Inet4Address && parts[0].contains(":") && parts[0].contains("."))
+            {
+                // Input string is in IPv4 mapped IPv6 format. InetAddress converted it to IPv4
+                // So adjust the net mask accordingly
+                // example, 0:0:0:0:0:ffff:192.1.56.10/96 would be converted to 192.1.56.10/0
+                netMask -= 96; // 6 * 16 bits
+            }
+        }
+        catch (UnknownHostException e)
+        {
+            throw new IllegalArgumentException(String.format("%s is not a valid CIDR String", cidrStr));
+        }
+
+        short maxMaskValue = maxNetMaskAllowed(ipAddress);
+        if (netMask < 0 || netMask > maxMaskValue)
+        {
+            throw new IllegalArgumentException(String.format("%s is not a valid CIDR String", cidrStr));
+        }
+
+        return new CIDR(ipAddress, netMask);
+    }
+
+    private static short maxNetMaskAllowed(InetAddress ipAddress)
+    {
+        if (ipAddress instanceof Inet6Address)
+            return 128;
+
+        return 32;
+    }
+
+    /**
+     * This function calculates starting and ending IP of a CIDR.
+     * For example, for CIDR 10.20.33.40/24, starting IP is 10.20.33.0, ending IP is 10.20.33.255
+     * @param ipAddress IP address of the CIDR
+     * @param netMask   net mask of the CIDR
+     * @return pair of starting and ending IP of the CIDR
+     */
+    private static Pair<InetAddress, InetAddress> calcIpRangeOfCidr(InetAddress ipAddress, short netMask)
+    {
+        if (netMask > maxNetMaskAllowed(ipAddress))
+            throw new IllegalArgumentException("Invalid netmask " + netMask + " for IP " + ipAddress.getHostAddress());
+
+        // Starting and ending IP are same as CIDR's IP if net mask is 32 for IPv4, 128 for IPv6
+        if (netMask == maxNetMaskAllowed(ipAddress))
+            return Pair.create(ipAddress, ipAddress);
+
+        byte[] startIpBytes = ipAddress.getAddress();
+        byte[] endIpBytes = ipAddress.getAddress();
+
+        // netmask indicates number of bits to remain unchanged. Let's call that as netmask bit.
+        // Calculate the offset of the byte where netmask bit ends
+        int byteOffset = netMask / Byte.SIZE;
+
+        // Calculate the bit number within that byte, where netmask bit ends
+        int bitOffset = netMask % Byte.SIZE;
+
+        // In that byte, set bits after netmask bit to 0 for starting IP, to 1 for ending IP,
+        // keeping bits before including netmask bit as it is
+        int unsignedByte = Byte.toUnsignedInt(startIpBytes[byteOffset]);
+        startIpBytes[byteOffset] = (byte) (unsignedByte & (0xff << (Byte.SIZE - bitOffset)));
+        endIpBytes[byteOffset] = (byte) (unsignedByte | (0xFF >>> bitOffset));
+
+        // set all remaining bits after netmask to 0
+        for (byteOffset += 1; byteOffset < startIpBytes.length; byteOffset++)
+        {
+            startIpBytes[byteOffset] = 0;
+            endIpBytes[byteOffset] = (byte) 0xFF;
+        }
+
+        try
+        {
+            return Pair.create(InetAddress.getByAddress(startIpBytes), InetAddress.getByAddress(endIpBytes));
+        }
+        catch (UnknownHostException e)
+        {
+            throw new IllegalStateException("Invalid bytes for constructing IP", e);
+        }
+    }
+
+    /**
+     * Get starting IP of the CIDR
+     * @return returns IP address
+     */
+    public InetAddress getStartIpAddress()
+    {
+        return startIpAddress;
+    }
+
+    /**
+     * Get ending IP of the CIDR
+     * @return returns IP address
+     */
+    public InetAddress getEndIpAddress()
+    {
+        return endIpAddress;
+    }
+
+    /**
+     * Get netmask of the CIDR
+     * @return returns netmask as short
+     */
+    public short getNetMask()
+    {
+        return netMask;
+    }
+
+    /**
+     * Tells is this IPv4 format CIDR
+     * @return true if IPv4 CIDR, otherwise false
+     */
+    public boolean isIPv4()
+    {
+        return (startIpAddress instanceof Inet4Address);
+    }
+
+    /**
+     * Tells is this IPv6 format CIDR
+     * @return true if IPv6 CIDR, otherwise false
+     */
+    public boolean isIPv6()
+    {
+        return (startIpAddress instanceof Inet6Address);
+    }
+
+    public boolean equals(Object o)
+    {
+        if (this == o)
+            return true;
+
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        CIDR cidr = (CIDR) o;
+        return netMask == cidr.netMask &&
+               startIpAddress.equals(cidr.startIpAddress) &&
+               endIpAddress.equals(cidr.endIpAddress);
+    }
+
+    public int hashCode()
+    {
+        return Objects.hash(startIpAddress, endIpAddress, netMask);
+    }
+
+    /**
+     * Constructs CIDR as string
+     * @return returns CIDR as string
+     */
+    @Override
+    public String toString()
+    {
+        return this.startIpAddress.getHostAddress() + '/' + this.netMask;
+    }
+
+    public String asCqlTupleString()
+    {
+        return "('" + this.startIpAddress.getHostAddress() + "', " + this.netMask + ')';
+    }
+
+    /**
+     * Compare 2 IpAddresses objects lexicographically
+     * @return true if IP ranges overlap with each other; otherwise, return false
+     */
+    public static int compareIPs(InetAddress l, InetAddress r)
+    {
+        byte[] lBytes = l.getAddress();
+        byte[] rBytes = r.getAddress();
+
+        for (int i = 0; i < lBytes.length; i++)
+        {
+            int comp = Byte.toUnsignedInt(lBytes[i]) - Byte.toUnsignedInt(rBytes[i]);
+            if (comp != 0)
+            {
+                return comp;
+            }
+        }
+        return 0;
+    }
+
+    public static boolean overlaps(CIDR left, CIDR right)
+    {
+        // Sort IP ranges by the start address
+        CIDR lower = left;
+        CIDR higher = right;
+
+        if (compareIPs(left.startIpAddress, right.startIpAddress) > 0)
+        {
+            lower = right;
+            higher = left;
+        }
+
+        // Overlaps when lower end is >= to higher start address
+        return compareIPs(lower.endIpAddress, higher.startIpAddress) >= 0;
+    }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java
index eb0e3e01f2..aadf6c788d 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java
@@ -36,18 +36,21 @@ public class AlterRoleStatement extends AuthenticationStatement
     private final RoleResource role;
     private final RoleOptions opts;
     final DCPermissions dcPermissions;
+    final CIDRPermissions cidrPermissions;
     private final boolean ifExists;
 
     public AlterRoleStatement(RoleName name, RoleOptions opts)
     {
-        this(name, opts, null, false);
+        this(name, opts, null, null, false);
     }
 
-    public AlterRoleStatement(RoleName name, RoleOptions opts, DCPermissions dcPermissions, boolean ifExists)
+    public AlterRoleStatement(RoleName name, RoleOptions opts, DCPermissions dcPermissions,
+                              CIDRPermissions cidrPermissions, boolean ifExists)
     {
         this.role = RoleResource.role(name.getName());
         this.opts = opts;
         this.dcPermissions = dcPermissions;
+        this.cidrPermissions = cidrPermissions;
         this.ifExists = ifExists;
     }
 
@@ -55,13 +58,19 @@ public class AlterRoleStatement extends AuthenticationStatement
     {
         opts.validate();
 
+        if (opts.isEmpty() && dcPermissions == null && cidrPermissions == null)
+            throw new InvalidRequestException("ALTER [ROLE|USER] can't be empty");
+
         if (dcPermissions != null)
         {
             dcPermissions.validate();
         }
 
-        if (opts.isEmpty() && dcPermissions == null)
-            throw new InvalidRequestException("ALTER [ROLE|USER] can't be empty");
+        if (cidrPermissions != null)
+        {
+            // Ensure input CIDR group names are valid, i.e, existing in CIDR groups mapping table
+            cidrPermissions.validate();
+        }
 
         // validate login here before authorize to avoid leaking user existence to anonymous users.
         state.ensureNotAnonymous();
@@ -107,8 +116,13 @@ public class AlterRoleStatement extends AuthenticationStatement
     {
         if (!opts.isEmpty())
             DatabaseDescriptor.getRoleManager().alterRole(state.getUser(), role, opts);
+
         if (dcPermissions != null)
             DatabaseDescriptor.getNetworkAuthorizer().setRoleDatacenters(role, dcPermissions);
+
+        if (cidrPermissions != null)
+            DatabaseDescriptor.getCIDRAuthorizer().setCidrGroupsForRole(role, cidrPermissions);
+
         return null;
     }
 
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java
index 04f183d7bb..d6e0a1298c 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java
@@ -34,13 +34,16 @@ public class CreateRoleStatement extends AuthenticationStatement
     private final RoleResource role;
     private final RoleOptions opts;
     final DCPermissions dcPermissions;
+    final CIDRPermissions cidrPermissions;
     private final boolean ifNotExists;
 
-    public CreateRoleStatement(RoleName name, RoleOptions options, DCPermissions dcPermissions, boolean ifNotExists)
+    public CreateRoleStatement(RoleName name, RoleOptions options, DCPermissions dcPermissions,
+                               CIDRPermissions cidrPermissions, boolean ifNotExists)
     {
         this.role = RoleResource.role(name.getName());
         this.opts = options;
         this.dcPermissions = dcPermissions;
+        this.cidrPermissions = cidrPermissions;
         this.ifNotExists = ifNotExists;
     }
 
@@ -58,13 +61,18 @@ public class CreateRoleStatement extends AuthenticationStatement
     {
         opts.validate();
 
+        if (role.getRoleName().isEmpty())
+            throw new InvalidRequestException("Role name can't be an empty string");
+
         if (dcPermissions != null)
         {
             dcPermissions.validate();
         }
 
-        if (role.getRoleName().isEmpty())
-            throw new InvalidRequestException("Role name can't be an empty string");
+        if (cidrPermissions != null)
+        {
+            cidrPermissions.validate();
+        }
 
         // validate login here before authorize to avoid leaking role existence to anonymous users.
         state.ensureNotAnonymous();
@@ -84,6 +92,10 @@ public class CreateRoleStatement extends AuthenticationStatement
         {
             DatabaseDescriptor.getNetworkAuthorizer().setRoleDatacenters(role, dcPermissions);
         }
+
+        if (cidrPermissions != null)
+            DatabaseDescriptor.getCIDRAuthorizer().setCidrGroupsForRole(role, cidrPermissions);
+
         grantPermissionsToCreator(state);
 
         return null;
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java
index 058ab01c04..13ba54a52d 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java
@@ -75,6 +75,7 @@ public class DropRoleStatement extends AuthenticationStatement
         DatabaseDescriptor.getAuthorizer().revokeAllFrom(role);
         DatabaseDescriptor.getAuthorizer().revokeAllOn(role);
         DatabaseDescriptor.getNetworkAuthorizer().drop(role);
+        DatabaseDescriptor.getCIDRAuthorizer().dropCidrPermissionsForRole(role);
         return null;
     }
     
diff --git a/src/java/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTable.java b/src/java/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTable.java
new file mode 100644
index 0000000000..580dcdd1fd
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTable.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.db.virtual;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Snapshot;
+import org.apache.cassandra.auth.CassandraAuthorizer;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.cql3.statements.SelectStatement;
+import org.apache.cassandra.db.marshal.DoubleType;
+import org.apache.cassandra.db.marshal.LongType;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.dht.LocalPartitioner;
+import org.apache.cassandra.metrics.CIDRAuthorizerMetrics;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.transport.messages.ResultMessage;
+import org.apache.cassandra.utils.MBeanWrapper;
+
+import static org.apache.cassandra.service.QueryState.forInternalCalls;
+import static org.apache.cassandra.utils.Clock.Global.nanoTime;
+
+/**
+ * Virtual tables capturing metrics related to CIDR filtering
+ */
+public class CIDRFilteringMetricsTable implements CIDRFilteringMetricsTableMBean
+{
+    public static final String MBEAN_NAME = "org.apache.cassandra.db:type=CIDRFilteringMetricsTable";
+
+    private static final CIDRFilteringMetricsTable instance = new CIDRFilteringMetricsTable();
+
+    CIDRFilteringMetricsTable()
+    {
+        // Register mbean for nodetool to access vtable entries
+        if (!MBeanWrapper.instance.isRegistered(MBEAN_NAME))
+            MBeanWrapper.instance.registerMBean(this, MBEAN_NAME);
+    }
+
+    public static Collection<VirtualTable> getAll(String keyspace)
+    {
+        return Arrays.asList(
+        new CIDRFilteringMetricsCountsTable(keyspace),
+        new CIDRFilteringMetricsLatenciesTable(keyspace)
+        );
+    }
+
+    /**
+     * Virtual table capturing counts i.e, non-latency metrics related to CIDR filtering
+     */
+    public static class CIDRFilteringMetricsCountsTable extends AbstractVirtualTable
+    {
+        public static final String TABLE_NAME = "cidr_filtering_metrics_counts";
+
+        public static final String NAME_COL = "name";
+        public static final String VALUE_COL = "value";
+        public static final String CIDR_ACCESSES_ACCEPTED_COUNT_NAME_PREFIX = "Number of CIDR accesses accepted from CIDR group - ";
+        public static final String CIDR_ACCESSES_REJECTED_COUNT_NAME_PREFIX = "Number of CIDR accesses rejected from CIDR group - ";
+        public static final String CIDR_GROUPS_CACHE_RELOAD_COUNT_NAME = "CIDR groups cache reload count";
+
+        @VisibleForTesting
+        CIDRFilteringMetricsCountsTable(String keyspace)
+        {
+            super(TableMetadata.builder(keyspace, TABLE_NAME)
+                               .comment("Count metrics specific to CIDR filtering")
+                               .kind(TableMetadata.Kind.VIRTUAL)
+                               .partitioner(new LocalPartitioner(UTF8Type.instance))
+                               .addPartitionKeyColumn(NAME_COL, UTF8Type.instance)
+                               .addRegularColumn(VALUE_COL, LongType.instance)
+                               .build());
+        }
+
+        private void addRow(SimpleDataSet dataSet, String name, long value)
+        {
+            dataSet.row(name)
+                   .column(VALUE_COL, value);
+        }
+
+        @Override
+        public DataSet data()
+        {
+            SimpleDataSet result = new SimpleDataSet(metadata());
+
+            CIDRAuthorizerMetrics cidrAuthorizerMetrics = DatabaseDescriptor.getCIDRAuthorizer().getCidrAuthorizerMetrics();
+
+            for (Map.Entry<String, Counter> entry : cidrAuthorizerMetrics.acceptedCidrAccessCount.entrySet())
+            {
+                addRow(result, CIDR_ACCESSES_ACCEPTED_COUNT_NAME_PREFIX + entry.getKey(),
+                       entry.getValue().getCount());
+            }
+
+            for (Map.Entry<String, Counter> entry : cidrAuthorizerMetrics.rejectedCidrAccessCount.entrySet())
+            {
+                addRow(result, CIDR_ACCESSES_REJECTED_COUNT_NAME_PREFIX + entry.getKey(),
+                       entry.getValue().getCount());
+            }
+
+            addRow(result, CIDR_GROUPS_CACHE_RELOAD_COUNT_NAME, cidrAuthorizerMetrics.cacheReloadCount.getCount());
+
+            return result;
+        }
+    }
+
+    /**
+     * Virtual table capturing latency metrics related to CIDR filtering
+     */
+    public static class CIDRFilteringMetricsLatenciesTable extends AbstractVirtualTable
+    {
+        public static final String TABLE_NAME = "cidr_filtering_metrics_latencies";
+
+        public static final String NAME_COL = "name";
+        public static final String P50_COL = "p50th";
+        public static final String P95_COL = "p95th";
+        public static final String P99_COL = "p99th";
+        public static final String P999_COL = "p999th";
+        public static final String MAX_COL = "max";
+
+        public static final String CIDR_CHECKS_LATENCY_NAME = "CIDR checks latency (ns)";
+        public static final String CIDR_GROUPS_CACHE_RELOAD_LATENCY_NAME = "CIDR groups cache reload latency (ns)";
+        public static final String LOOKUP_CIDR_GROUPS_FOR_IP_LATENCY_NAME = "Lookup IP in CIDR groups cache latency (ns)";
+
+        @VisibleForTesting
+        CIDRFilteringMetricsLatenciesTable(String keyspace)
+        {
+            super(TableMetadata.builder(keyspace, TABLE_NAME)
+                               .comment("Latency metrics specific to CIDR filtering")
+                               .kind(TableMetadata.Kind.VIRTUAL)
+                               .partitioner(new LocalPartitioner(UTF8Type.instance))
+                               .addPartitionKeyColumn(NAME_COL, UTF8Type.instance)
+                               .addRegularColumn(P50_COL, DoubleType.instance)
+                               .addRegularColumn(P95_COL, DoubleType.instance)
+                               .addRegularColumn(P99_COL, DoubleType.instance)
+                               .addRegularColumn(P999_COL, DoubleType.instance)
+                               .addRegularColumn(MAX_COL, DoubleType.instance)
+                               .build());
+        }
+
+        private void addRow(SimpleDataSet dataSet, String name, Snapshot snapshot)
+        {
+            dataSet.row(name)
+                   .column(P50_COL, snapshot.getMedian())
+                   .column(P95_COL, snapshot.get95thPercentile())
+                   .column(P99_COL, snapshot.get99thPercentile())
+                   .column(P999_COL, snapshot.get999thPercentile())
+                   .column(MAX_COL, (double) snapshot.getMax());
+        }
+
+        @Override
+        public DataSet data()
+        {
+            SimpleDataSet result = new SimpleDataSet(metadata());
+
+            CIDRAuthorizerMetrics cidrAuthorizerMetrics =
+            DatabaseDescriptor.getCIDRAuthorizer().getCidrAuthorizerMetrics();
+
+            addRow(result, CIDR_CHECKS_LATENCY_NAME, cidrAuthorizerMetrics.cidrChecksLatency.getSnapshot());
+            addRow(result, CIDR_GROUPS_CACHE_RELOAD_LATENCY_NAME,
+                   cidrAuthorizerMetrics.cacheReloadLatency.getSnapshot());
+            addRow(result, LOOKUP_CIDR_GROUPS_FOR_IP_LATENCY_NAME,
+                   cidrAuthorizerMetrics.lookupCidrGroupsForIpLatency.getSnapshot());
+
+            return result;
+        }
+    }
+
+    private UntypedResultSet retrieveRows(SelectStatement statement)
+    {
+        QueryOptions options = QueryOptions.forInternalCalls(CassandraAuthorizer.authReadConsistencyLevel(),
+                                                             Collections.emptyList());
+
+        ResultMessage.Rows rows = statement.execute(forInternalCalls(), options, nanoTime());
+        return UntypedResultSet.create(rows.result);
+    }
+
+    public Map<String, Long> getCountsMetricsFromVtable()
+    {
+        String countsMetricsTableName = SchemaConstants.VIRTUAL_VIEWS + '.' +
+                                        CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable.TABLE_NAME;
+
+        SelectStatement getCountsMetricsStatement =
+            (SelectStatement) QueryProcessor.getStatement(String.format("SELECT * FROM %s", countsMetricsTableName),
+                                                          ClientState.forInternalCalls());
+
+        Map<String, Long> metrics = new HashMap<>();
+
+        UntypedResultSet result = retrieveRows(getCountsMetricsStatement);
+        for (UntypedResultSet.Row row : result)
+        {
+            if (!row.has(CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable.NAME_COL) ||
+                !row.has(CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable.VALUE_COL))
+                throw new RuntimeException("Invalid row " + row + " in table: " + countsMetricsTableName);
+
+            metrics.put(row.getString(CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable.NAME_COL),
+                        row.getLong(CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable.VALUE_COL));
+        }
+
+        return metrics;
+    }
+
+    public Map<String, List<Double>> getLatenciesMetricsFromVtable()
+    {
+        String latenciesMetricsTableName = SchemaConstants.VIRTUAL_VIEWS + '.' +
+                                           CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable.TABLE_NAME;
+
+        SelectStatement getLatenciesMetricsStatement =
+            (SelectStatement) QueryProcessor.getStatement(String.format("SELECT * FROM %s", latenciesMetricsTableName),
+                                                          ClientState.forInternalCalls());
+
+        Map<String, List<Double>> metrics = new HashMap<>();
+
+        UntypedResultSet result = retrieveRows(getLatenciesMetricsStatement);
+        for (UntypedResultSet.Row row : result)
+        {
+            if (!row.has(CIDRFilteringMetricsLatenciesTable.NAME_COL) ||
+                !row.has(CIDRFilteringMetricsLatenciesTable.P50_COL))
+                throw new RuntimeException("Invalid row " + row + " in table: " + latenciesMetricsTableName);
+
+            metrics.put(row.getString(CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable.NAME_COL),
+                        Arrays.asList(row.getDouble(CIDRFilteringMetricsLatenciesTable.P50_COL),
+                                      row.getDouble(CIDRFilteringMetricsLatenciesTable.P95_COL),
+                                      row.getDouble(CIDRFilteringMetricsLatenciesTable.P99_COL),
+                                      row.getDouble(CIDRFilteringMetricsLatenciesTable.P999_COL),
+                                      row.getDouble(CIDRFilteringMetricsLatenciesTable.MAX_COL)));
+        }
+
+        return metrics;
+    }
+}
diff --git a/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java b/src/java/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTableMBean.java
similarity index 51%
copy from test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java
copy to src/java/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTableMBean.java
index 51c61bbb12..b90ed3c818 100644
--- a/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java
+++ b/src/java/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTableMBean.java
@@ -16,32 +16,25 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.cql3.statements;
+package org.apache.cassandra.db.virtual;
 
-import org.junit.Assert;
-import org.junit.Test;
+import java.util.List;
+import java.util.Map;
 
-import org.apache.cassandra.auth.DCPermissions;
-import org.apache.cassandra.cql3.CQLStatement;
-import org.apache.cassandra.cql3.QueryProcessor;
-
-public class CreateUserStatementTest
+/**
+ * mbean to expose CIDR filtering metrics virtual table to JMX/nodetool
+ */
+public interface CIDRFilteringMetricsTableMBean
 {
-    private static CreateRoleStatement parse(String query)
-    {
-        CQLStatement.Raw stmt = QueryProcessor.parseStatement(query);
-        Assert.assertTrue(stmt instanceof CreateRoleStatement);
-        return (CreateRoleStatement) stmt;
-    }
-
-    private static DCPermissions dcPerms(String query)
-    {
-        return parse(query).dcPermissions;
-    }
+    /**
+     * Retreive rows from CIDR filtering counts metrics virtual table
+     * @return map of rows, map's key is metric name, map's value is metric value
+     */
+    public Map<String, Long> getCountsMetricsFromVtable();
 
-    @Test
-    public void allDcsImplicit() throws Exception
-    {
-        Assert.assertFalse(dcPerms("CREATE USER u1").restrictsAccess());
-    }
+    /**
+     * Retreive rows from CIDR filtering latency metrics virtual table
+     * @return map of rows, map's key is metric name, map's values are latency percentiles
+     */
+    public Map<String, List<Double>> getLatenciesMetricsFromVtable();
 }
diff --git a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
index 6a718c7145..7d6152bdc2 100644
--- a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
+++ b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
@@ -53,6 +53,7 @@ public final class SystemViewsKeyspace extends VirtualKeyspace
                     .add(new LogMessagesTable(VIRTUAL_VIEWS))
                     .add(new SnapshotsTable(VIRTUAL_VIEWS))
                     .addAll(LocalRepairTables.getAll(VIRTUAL_VIEWS))
+                    .addAll(CIDRFilteringMetricsTable.getAll(VIRTUAL_VIEWS))
                     .addAll(StorageAttachedIndexTables.getAll(VIRTUAL_VIEWS))
                     .build());
     }
diff --git a/src/java/org/apache/cassandra/metrics/CIDRAuthorizerMetrics.java b/src/java/org/apache/cassandra/metrics/CIDRAuthorizerMetrics.java
new file mode 100644
index 0000000000..0de51dfb09
--- /dev/null
+++ b/src/java/org/apache/cassandra/metrics/CIDRAuthorizerMetrics.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.metrics;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Timer;
+
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
+
+/**
+ * Captures metrics of CIDR authorizer
+ */
+public class CIDRAuthorizerMetrics
+{
+    private static final MetricNameFactory factory = new DefaultNameFactory("CIDRAuthorizer");
+
+    // Number of user accesses rejected by CIDR authorization
+    public static final String CIDR_ACCESSES_REJECTED_COUNT_PREFIX = "CIDRAccessesRejectedCount - ";
+    public static final String CIDR_ACCESSES_ACCEPTED_COUNT_PREFIX = "CIDRAccessesAcceptedCount - ";
+    // Time taken by CIDR authorization checks
+    public static final String CIDR_CHECKS_LATENCY = "CIDRChecksLatency";
+
+    // Number of times CIDR groups cache was (re)loaded
+    public static final String CIDR_GROUPS_CACHE_RELOAD_COUNT = "CIDRGroupsMappingCacheReloadCount";
+    // Time taken to reload CIDR groups cache
+    public static final String CIDR_GROUPS_CACHE_RELOAD_LATENCY = "CIDRGroupsMappingCacheReloadLatency";
+    // Time taken to look up an IP in CIDR groups cache, to find best matching CIDR group
+    public static final String LOOKUP_CIDR_GROUPS_FOR_IP_LATENCY = "LookupCIDRGroupsForIPLatency";
+
+    public final Timer cidrChecksLatency;
+    public final Counter cacheReloadCount;
+    public final Timer cacheReloadLatency;
+    public final Timer lookupCidrGroupsForIpLatency;
+
+    public final ConcurrentHashMap<String, Counter> rejectedCidrAccessCount = new ConcurrentHashMap<>();
+    public final ConcurrentHashMap<String, Counter> acceptedCidrAccessCount = new ConcurrentHashMap<>();
+
+    public CIDRAuthorizerMetrics()
+    {
+        cidrChecksLatency = Metrics.timer(factory.createMetricName(CIDR_CHECKS_LATENCY));
+        cacheReloadCount = Metrics.counter(factory.createMetricName(CIDR_GROUPS_CACHE_RELOAD_COUNT));
+        cacheReloadLatency = Metrics.timer(factory.createMetricName(CIDR_GROUPS_CACHE_RELOAD_LATENCY));
+        lookupCidrGroupsForIpLatency = Metrics.timer(factory.createMetricName(LOOKUP_CIDR_GROUPS_FOR_IP_LATENCY));
+    }
+
+    private void incCounter(ConcurrentHashMap<String, Counter> metric, String metricPrefix, String cidrGroup)
+    {
+        metric.computeIfAbsent(cidrGroup,
+                               k -> Metrics.counter(factory.createMetricName(metricPrefix + cidrGroup))).inc();
+    }
+
+    public void incrRejectedAccessCount(Set<String> cidrGroups)
+    {
+        if (cidrGroups == null || cidrGroups.isEmpty())
+        {
+            incCounter(rejectedCidrAccessCount, CIDR_ACCESSES_REJECTED_COUNT_PREFIX, "Undefined");
+            return;
+        }
+
+        for (String cidrGroup : cidrGroups)
+        {
+            incCounter(rejectedCidrAccessCount, CIDR_ACCESSES_REJECTED_COUNT_PREFIX, cidrGroup);
+        }
+    }
+
+    public void incrAcceptedAccessCount(Set<String> cidrGroups)
+    {
+        if (cidrGroups == null || cidrGroups.isEmpty())
+        {
+            incCounter(acceptedCidrAccessCount, CIDR_ACCESSES_ACCEPTED_COUNT_PREFIX, "Undefined");
+            return;
+        }
+
+        for (String cidrGroup : cidrGroups)
+        {
+            incCounter(acceptedCidrAccessCount, CIDR_ACCESSES_ACCEPTED_COUNT_PREFIX, cidrGroup);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/service/ClientState.java b/src/java/org/apache/cassandra/service/ClientState.java
index f03a9712e8..d9a0dbbc9e 100644
--- a/src/java/org/apache/cassandra/service/ClientState.java
+++ b/src/java/org/apache/cassandra/service/ClientState.java
@@ -534,6 +534,11 @@ public class ClientState
         {
             throw new UnauthorizedException(String.format("You do not have access to this datacenter (%s)", Datacenters.thisDatacenter()));
         }
+        else
+        {
+            if (remoteAddress != null && !user.hasAccessFromIp(remoteAddress))
+                throw new UnauthorizedException("You do not have access from this IP " + remoteAddress.getHostString());
+        }
     }
 
     public void ensureNotAnonymous()
diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java
index c3c5fbba09..28a8c80d09 100644
--- a/src/java/org/apache/cassandra/service/StorageService.java
+++ b/src/java/org/apache/cassandra/service/StorageService.java
@@ -128,11 +128,12 @@ import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
 import org.apache.cassandra.dht.BootStrapper;
 import org.apache.cassandra.dht.IPartitioner;
 import org.apache.cassandra.dht.Range;
+import org.apache.cassandra.dht.Token.TokenFactory;
+import org.apache.cassandra.hints.HintsService;
 import org.apache.cassandra.dht.RangeStreamer;
 import org.apache.cassandra.dht.RangeStreamer.FetchReplica;
 import org.apache.cassandra.dht.StreamStateStore;
 import org.apache.cassandra.dht.Token;
-import org.apache.cassandra.dht.Token.TokenFactory;
 import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.exceptions.UnavailableException;
@@ -147,7 +148,6 @@ import org.apache.cassandra.gms.IEndpointStateChangeSubscriber;
 import org.apache.cassandra.gms.IFailureDetector;
 import org.apache.cassandra.gms.TokenSerializer;
 import org.apache.cassandra.gms.VersionedValue;
-import org.apache.cassandra.hints.HintsService;
 import org.apache.cassandra.index.IndexStatusManager;
 import org.apache.cassandra.io.sstable.IScrubber;
 import org.apache.cassandra.io.sstable.IVerifier;
@@ -1374,6 +1374,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
             DatabaseDescriptor.getAuthenticator().setup();
             DatabaseDescriptor.getAuthorizer().setup();
             DatabaseDescriptor.getNetworkAuthorizer().setup();
+            DatabaseDescriptor.getCIDRAuthorizer().setup();
             AuthCacheService.initializeAndRegisterCaches();
             Schema.instance.registerListener(new AuthSchemaChangeListener());
             authSetupComplete = true;
diff --git a/src/java/org/apache/cassandra/tools/ListCIDRGroups.java b/src/java/org/apache/cassandra/tools/ListCIDRGroups.java
new file mode 100644
index 0000000000..4cc3097512
--- /dev/null
+++ b/src/java/org/apache/cassandra/tools/ListCIDRGroups.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.tools;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.auth.AuthKeyspace;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+/**
+ * Nodetool command to list available CIDR groups, in the table {@link AuthKeyspace#CIDR_GROUPS}
+ */
+@Command(name = "listcidrgroups", description = "List existing cidr groups")
+public class ListCIDRGroups extends NodeToolCmd
+{
+    @Arguments(usage = "[<cidrGroup>]", description = "LIST operation can be invoked with or without cidr group name")
+    private List<String> args = new ArrayList<>();
+
+    @Override
+    public void execute(NodeProbe probe)
+    {
+        PrintStream out = probe.output().out;
+
+        if (args.size() < 1)
+        {
+            probe.printSet(out, "CIDR Groups", probe.listAvailableCidrGroups());
+            return;
+        }
+
+        String cidrGroup = args.get(0);
+        probe.printSet(out, "CIDRs", probe.listCidrsOfCidrGroup(cidrGroup));
+    }
+}
diff --git a/src/java/org/apache/cassandra/tools/NodeProbe.java b/src/java/org/apache/cassandra/tools/NodeProbe.java
index 6f8035ae4e..1adc15df95 100644
--- a/src/java/org/apache/cassandra/tools/NodeProbe.java
+++ b/src/java/org/apache/cassandra/tools/NodeProbe.java
@@ -59,9 +59,14 @@ import org.apache.cassandra.audit.AuditLogManager;
 import org.apache.cassandra.audit.AuditLogManagerMBean;
 import org.apache.cassandra.audit.AuditLogOptions;
 import org.apache.cassandra.audit.AuditLogOptionsCompositeData;
+
 import com.google.common.collect.ImmutableMap;
 import org.apache.cassandra.auth.AuthCache;
 import org.apache.cassandra.auth.AuthCacheMBean;
+import org.apache.cassandra.auth.CIDRGroupsMappingManager;
+import org.apache.cassandra.auth.CIDRGroupsMappingManagerMBean;
+import org.apache.cassandra.auth.CIDRPermissionsManager;
+import org.apache.cassandra.auth.CIDRPermissionsManagerMBean;
 import org.apache.cassandra.auth.NetworkPermissionsCache;
 import org.apache.cassandra.auth.NetworkPermissionsCacheMBean;
 import org.apache.cassandra.auth.PasswordAuthenticator;
@@ -75,6 +80,8 @@ import org.apache.cassandra.batchlog.BatchlogManagerMBean;
 import org.apache.cassandra.db.ColumnFamilyStoreMBean;
 import org.apache.cassandra.db.compaction.CompactionManager;
 import org.apache.cassandra.db.compaction.CompactionManagerMBean;
+import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable;
+import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTableMBean;
 import org.apache.cassandra.fql.FullQueryLoggerOptions;
 import org.apache.cassandra.fql.FullQueryLoggerOptionsCompositeData;
 import org.apache.cassandra.gms.FailureDetector;
@@ -85,6 +92,7 @@ import org.apache.cassandra.hints.HintsService;
 import org.apache.cassandra.hints.HintsServiceMBean;
 import org.apache.cassandra.locator.DynamicEndpointSnitchMBean;
 import org.apache.cassandra.locator.EndpointSnitchInfoMBean;
+import org.apache.cassandra.metrics.CIDRAuthorizerMetrics;
 import org.apache.cassandra.metrics.CassandraMetricsRegistry;
 import org.apache.cassandra.metrics.StorageMetrics;
 import org.apache.cassandra.metrics.TableMetrics;
@@ -102,6 +110,7 @@ import org.apache.cassandra.service.StorageServiceMBean;
 import org.apache.cassandra.streaming.StreamManagerMBean;
 import org.apache.cassandra.streaming.StreamState;
 import org.apache.cassandra.streaming.management.StreamStateCompositeData;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
 
 import com.codahale.metrics.JmxReporter;
 import com.google.common.base.Function;
@@ -155,11 +164,15 @@ public class NodeProbe implements AutoCloseable
     protected PasswordAuthenticator.CredentialsCacheMBean ccProxy;
     protected AuthorizationProxy.JmxPermissionsCacheMBean jpcProxy;
     protected NetworkPermissionsCacheMBean npcProxy;
+    protected CIDRPermissionsManagerMBean cpbProxy;
+    protected CIDRGroupsMappingManagerMBean cmbProxy;
     protected PermissionsCacheMBean pcProxy;
     protected RolesCacheMBean rcProxy;
     protected Output output;
     private boolean failed;
 
+    protected CIDRFilteringMetricsTableMBean cfmProxy;
+
     /**
      * Creates a NodeProbe using the specified JMX host, port, username, and password.
      *
@@ -275,12 +288,24 @@ public class NodeProbe implements AutoCloseable
             ccProxy = JMX.newMBeanProxy(mbeanServerConn, name, PasswordAuthenticator.CredentialsCacheMBean.class);
             name = new ObjectName(AuthCache.MBEAN_NAME_BASE + AuthorizationProxy.JmxPermissionsCacheMBean.CACHE_NAME);
             jpcProxy = JMX.newMBeanProxy(mbeanServerConn, name, AuthorizationProxy.JmxPermissionsCacheMBean.class);
+
             name = new ObjectName(AuthCache.MBEAN_NAME_BASE + NetworkPermissionsCache.CACHE_NAME);
             npcProxy = JMX.newMBeanProxy(mbeanServerConn, name, NetworkPermissionsCacheMBean.class);
+
             name = new ObjectName(AuthCache.MBEAN_NAME_BASE + PermissionsCache.CACHE_NAME);
             pcProxy = JMX.newMBeanProxy(mbeanServerConn, name, PermissionsCacheMBean.class);
+
             name = new ObjectName(AuthCache.MBEAN_NAME_BASE + RolesCache.CACHE_NAME);
             rcProxy = JMX.newMBeanProxy(mbeanServerConn, name, RolesCacheMBean.class);
+
+            name = new ObjectName(CIDRPermissionsManager.MBEAN_NAME);
+            cpbProxy = JMX.newMBeanProxy(mbeanServerConn, name, CIDRPermissionsManagerMBean.class);
+
+            name = new ObjectName(CIDRGroupsMappingManager.MBEAN_NAME);
+            cmbProxy = JMX.newMBeanProxy(mbeanServerConn, name, CIDRGroupsMappingManagerMBean.class);
+
+            name = new ObjectName(CIDRFilteringMetricsTable.MBEAN_NAME);
+            cfmProxy = JMX.newMBeanProxy(mbeanServerConn, name, CIDRFilteringMetricsTableMBean.class);
         }
         catch (MalformedObjectNameException e)
         {
@@ -592,6 +617,41 @@ public class NodeProbe implements AutoCloseable
         npcProxy.invalidateNetworkPermissions(roleName);
     }
 
+    public boolean invalidateCidrPermissionsCache(String roleName)
+    {
+        return cpbProxy.invalidateCidrPermissionsCache(roleName);
+    }
+
+    public void reloadCidrGroupsCache()
+    {
+        cmbProxy.loadCidrGroupsCache();
+    }
+
+    public Set<String> listAvailableCidrGroups()
+    {
+        return cmbProxy.getAvailableCidrGroups();
+    }
+
+    public Set<String> listCidrsOfCidrGroup(String cidrGroup)
+    {
+        return cmbProxy.getCidrsOfCidrGroupAsStrings(cidrGroup);
+    }
+
+    public void updateCidrGroup(String cidrGroupName, List<String> cidrs)
+    {
+        cmbProxy.updateCidrGroup(cidrGroupName, cidrs);
+    }
+
+    public void dropCidrGroup(String cidrGroupName)
+    {
+        cmbProxy.dropCidrGroup(cidrGroupName);
+    }
+
+    public Set<String> getCidrGroupsOfIp(String ipStr)
+    {
+        return cmbProxy.getCidrGroupsOfIP(ipStr);
+    }
+
     public void invalidatePermissionsCache()
     {
         pcProxy.invalidate();
@@ -1981,6 +2041,57 @@ public class NodeProbe implements AutoCloseable
         }
     }
 
+    public Object getCidrFilteringMetric(String metricName)
+    {
+        try
+        {
+            switch(metricName)
+            {
+                case CIDRAuthorizerMetrics.CIDR_CHECKS_LATENCY:
+                    return JMX.newMBeanProxy(mbeanServerConn,
+                                             new ObjectName("org.apache.cassandra.metrics:type=CIDRAuthorization,name="
+                                                            + metricName),
+                                             CassandraMetricsRegistry.JmxTimerMBean.class).getMean();
+                case CIDRAuthorizerMetrics.CIDR_GROUPS_CACHE_RELOAD_COUNT:
+                    return JMX.newMBeanProxy(
+                        mbeanServerConn,
+                        new ObjectName("org.apache.cassandra.metrics:type=CIDRGroupsMappingCache,name=" + metricName),
+                        CassandraMetricsRegistry.JmxCounterMBean.class).getCount();
+                case CIDRAuthorizerMetrics.CIDR_GROUPS_CACHE_RELOAD_LATENCY:
+                case CIDRAuthorizerMetrics.LOOKUP_CIDR_GROUPS_FOR_IP_LATENCY:
+                    return JMX.newMBeanProxy(
+                        mbeanServerConn,
+                        new ObjectName("org.apache.cassandra.metrics:type=CIDRGroupsMappingCache,name=" + metricName),
+                        CassandraMetricsRegistry.JmxTimerMBean.class).getMean();
+                default:
+                    if (metricName.contains(CIDRAuthorizerMetrics.CIDR_ACCESSES_REJECTED_COUNT_PREFIX) ||
+                        metricName.contains(CIDRAuthorizerMetrics.CIDR_ACCESSES_ACCEPTED_COUNT_PREFIX))
+                    {
+                        return JMX.newMBeanProxy(
+                            mbeanServerConn,
+                            new ObjectName("org.apache.cassandra.metrics:type=mymetricname,name=" + metricName),
+                            CassandraMetricsRegistry.JmxCounterMBean.class).getCount();
+                    }
+
+                    throw new RuntimeException("Unknown metric " + metricName);
+            }
+        }
+        catch (MalformedObjectNameException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public Map<String, Long> getCountsMetricsFromVtable()
+    {
+        return cfmProxy.getCountsMetricsFromVtable();
+    }
+
+    public Map<String, List<Double>> getLatenciesMetricsFromVtable()
+    {
+        return cfmProxy.getLatenciesMetricsFromVtable();
+    }
+
     /**
      * Retrieve Proxy metrics
      * @param metricName Exceptions, Load, TotalHints or TotalHintsInProgress.
@@ -2215,6 +2326,21 @@ public class NodeProbe implements AutoCloseable
     {
         return ssProxy.getDefaultKeyspaceReplicationFactor();
     }
+
+    public void printSet(PrintStream out, String colName, Set<String> values)
+    {
+        if (values == null || values.isEmpty())
+            return;
+
+        TableBuilder table = new TableBuilder();
+
+        table.add(colName + ": ");
+
+        for (String value : values)
+            table.add(value);
+
+        table.printTo(out);
+    }
 }
 
 class ColumnFamilyStoreMBeanIterator implements Iterator<Map.Entry<String, ColumnFamilyStoreMBean>>
diff --git a/src/java/org/apache/cassandra/tools/NodeTool.java b/src/java/org/apache/cassandra/tools/NodeTool.java
index 5500240477..9c4d7858fd 100644
--- a/src/java/org/apache/cassandra/tools/NodeTool.java
+++ b/src/java/org/apache/cassandra/tools/NodeTool.java
@@ -98,6 +98,7 @@ public class NodeTool
                 CassHelp.class,
                 CfHistograms.class,
                 CfStats.class,
+                CIDRFilteringStats.class,
                 Cleanup.class,
                 ClearSnapshot.class,
                 ClientStats.class,
@@ -118,6 +119,7 @@ public class NodeTool
                 DisableHintsForDC.class,
                 DisableOldProtocolVersions.class,
                 Drain.class,
+                DropCIDRGroup.class,
                 EnableAuditLog.class,
                 EnableAutoCompaction.class,
                 EnableBackup.class,
@@ -134,6 +136,7 @@ public class NodeTool
                 GetAuditLog.class,
                 GetAuthCacheConfig.class,
                 GetBatchlogReplayTrottle.class,
+                GetCIDRGroupsOfIP.class,
                 GetColumnIndexSize.class,
                 GetCompactionThreshold.class,
                 GetCompactionThroughput.class,
@@ -155,15 +158,18 @@ public class NodeTool
                 GossipInfo.class,
                 Import.class,
                 Info.class,
+                InvalidateCIDRPermissionsCache.class,
                 InvalidateCounterCache.class,
                 InvalidateCredentialsCache.class,
                 InvalidateJmxPermissionsCache.class,
+                ReloadCIDRGroupsCache.class,
                 InvalidateKeyCache.class,
                 InvalidateNetworkPermissionsCache.class,
                 InvalidatePermissionsCache.class,
                 InvalidateRolesCache.class,
                 InvalidateRowCache.class,
                 Join.class,
+                ListCIDRGroups.class,
                 ListPendingHints.class,
                 ListSnapshots.class,
                 Move.class,
@@ -224,6 +230,7 @@ public class NodeTool
                 TopPartitions.class,
                 TpStats.class,
                 TruncateHints.class,
+                UpdateCIDRGroup.class,
                 UpgradeSSTable.class,
                 Verify.class,
                 Version.class,
diff --git a/src/java/org/apache/cassandra/tools/nodetool/CIDRFilteringStats.java b/src/java/org/apache/cassandra/tools/nodetool/CIDRFilteringStats.java
new file mode 100644
index 0000000000..9b01dc7894
--- /dev/null
+++ b/src/java/org/apache/cassandra/tools/nodetool/CIDRFilteringStats.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.tools.nodetool;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable;
+import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
+
+/**
+ * Nodetool command to view stats related to CIDR filtering
+ */
+@Command(name = "cidrfilteringstats", description = "Print statistics on CIDR filtering")
+public class CIDRFilteringStats extends NodeToolCmd
+{
+    private void printCountsMetrics(NodeProbe probe, PrintStream out)
+    {
+        out.println(CIDRFilteringMetricsCountsTable.TABLE_NAME + ": ");
+        TableBuilder outputTable = new TableBuilder();
+        outputTable.add(CIDRFilteringMetricsCountsTable.NAME_COL,
+                        CIDRFilteringMetricsCountsTable.VALUE_COL);
+
+        Map<String, Long> metricRows = probe.getCountsMetricsFromVtable();
+
+        for (Map.Entry<String, Long> row : metricRows.entrySet())
+        {
+            outputTable.add(row.getKey(), Long.toString(row.getValue()));
+        }
+
+        outputTable.printTo(out);
+        out.println();
+    }
+
+    private void printLatenciesMetrics(NodeProbe probe, PrintStream out)
+    {
+        out.println(CIDRFilteringMetricsLatenciesTable.TABLE_NAME + ": ");
+        TableBuilder outputTable = new TableBuilder();
+        outputTable.add(CIDRFilteringMetricsLatenciesTable.NAME_COL,
+                        CIDRFilteringMetricsLatenciesTable.P50_COL,
+                        CIDRFilteringMetricsLatenciesTable.P95_COL,
+                        CIDRFilteringMetricsLatenciesTable.P99_COL,
+                        CIDRFilteringMetricsLatenciesTable.P999_COL,
+                        CIDRFilteringMetricsLatenciesTable.MAX_COL);
+
+        Map<String, List<Double>> metricRows = probe.getLatenciesMetricsFromVtable();
+
+        for (Map.Entry<String, List<Double>> row : metricRows.entrySet())
+        {
+            List<String> values = new ArrayList<>();
+            values.add(row.getKey());
+            for (Double col : row.getValue())
+            {
+                values.add(Double.toString(col));
+            }
+            outputTable.add(values);
+        }
+
+        outputTable.printTo(out);
+        out.println();
+    }
+
+    @Override
+    public void execute(NodeProbe probe)
+    {
+        PrintStream out = probe.output().out;
+
+        // Read rows from corresponding virtual tables and print
+        printCountsMetrics(probe, out);
+        printLatenciesMetrics(probe, out);
+    }
+}
diff --git a/src/java/org/apache/cassandra/tools/nodetool/DropCIDRGroup.java b/src/java/org/apache/cassandra/tools/nodetool/DropCIDRGroup.java
new file mode 100644
index 0000000000..2b432ea9f7
--- /dev/null
+++ b/src/java/org/apache/cassandra/tools/nodetool/DropCIDRGroup.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.tools.nodetool;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.auth.AuthKeyspace;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Nodetool command to drop a CIDR group and associated mapping from the table {@link AuthKeyspace#CIDR_GROUPS}
+ */
+@Command(name = "dropcidrgroup", description = "Drop an existing cidr group")
+public class DropCIDRGroup extends NodeToolCmd
+{
+    @Arguments(usage = "<cidrGroup>", description = "Requires a cidr group name")
+    private List<String> args = new ArrayList<>();
+
+    @Override
+    public void execute(NodeProbe probe)
+    {
+        checkArgument(args.size() == 1, "dropcidrgroup command requires a cidr group name");
+
+        String cidrGroupName = args.get(0);
+        probe.dropCidrGroup(cidrGroupName);
+
+        probe.output().out.println("Deleted CIDR group " + cidrGroupName);
+    }
+}
diff --git a/src/java/org/apache/cassandra/tools/nodetool/GetCIDRGroupsOfIP.java b/src/java/org/apache/cassandra/tools/nodetool/GetCIDRGroupsOfIP.java
new file mode 100644
index 0000000000..85c5194ed9
--- /dev/null
+++ b/src/java/org/apache/cassandra/tools/nodetool/GetCIDRGroupsOfIP.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.tools.nodetool;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Nodetool command to get CIDR groups(s) of given IP
+ */
+@Command(name = "getcidrgroupsofip", description = "Print CIDR groups associated with given IP")
+public class GetCIDRGroupsOfIP extends NodeToolCmd
+{
+    @Arguments(usage = "<IP address>", description = "Requires IP address as a string")
+    private List<String> args = new ArrayList<>();
+
+    @Override
+    public void execute(NodeProbe probe)
+    {
+        PrintStream out = probe.output().out;
+
+        checkArgument(args.size() == 1, "Requires IP address as input");
+
+        String ipStr = args.get(0);
+        probe.printSet(out, "CIDR Groups", probe.getCidrGroupsOfIp(ipStr));
+    }
+}
diff --git a/src/java/org/apache/cassandra/tools/nodetool/InvalidateCIDRPermissionsCache.java b/src/java/org/apache/cassandra/tools/nodetool/InvalidateCIDRPermissionsCache.java
new file mode 100644
index 0000000000..622bed3519
--- /dev/null
+++ b/src/java/org/apache/cassandra/tools/nodetool/InvalidateCIDRPermissionsCache.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.tools.nodetool;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+/**
+ * Nodetool command to invalidate CIDR permissions cache, for a give role or for all roles in the cache.
+ */
+@Command(name = "invalidatecidrpermissionscache", description = "Invalidate the cidr permissions cache")
+public class InvalidateCIDRPermissionsCache extends NodeToolCmd
+{
+    @Arguments(usage = "[<role>...]", description = "List of roles to invalidate. By default, all roles")
+    private List<String> args = new ArrayList<>();
+
+    @Override
+    public void execute(NodeProbe probe)
+    {
+        if (args.isEmpty())
+        {
+            probe.invalidateCidrPermissionsCache("");
+            probe.output().out.println("Invalidated CIDR permissions cache");
+        }
+        else
+        {
+            for (String roleName : args)
+            {
+                if (probe.invalidateCidrPermissionsCache(roleName))
+                    probe.output().out.println("Invalidated the role " + roleName + " from CIDR permissions cache");
+                else
+                    probe.output().out.println("Not found role " + roleName + " in CIDR permissions cache, nothing to invalidate");
+            }
+        }
+    }
+}
diff --git a/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java b/src/java/org/apache/cassandra/tools/nodetool/ReloadCIDRGroupsCache.java
similarity index 51%
copy from test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java
copy to src/java/org/apache/cassandra/tools/nodetool/ReloadCIDRGroupsCache.java
index 51c61bbb12..0cf44e7cf8 100644
--- a/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/ReloadCIDRGroupsCache.java
@@ -15,33 +15,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.cassandra.tools.nodetool;
 
-package org.apache.cassandra.cql3.statements;
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
 
-import org.junit.Assert;
-import org.junit.Test;
-
-import org.apache.cassandra.auth.DCPermissions;
-import org.apache.cassandra.cql3.CQLStatement;
-import org.apache.cassandra.cql3.QueryProcessor;
-
-public class CreateUserStatementTest
+/**
+ * Nodetool command to reload CIDR groups cache
+ */
+@Command(name = "reloadcidrgroupscache", description = "Reload CIDR groups cache with latest entries in cidr_groups table, when CIDR authorizer is enabled")
+public class ReloadCIDRGroupsCache extends NodeToolCmd
 {
-    private static CreateRoleStatement parse(String query)
-    {
-        CQLStatement.Raw stmt = QueryProcessor.parseStatement(query);
-        Assert.assertTrue(stmt instanceof CreateRoleStatement);
-        return (CreateRoleStatement) stmt;
-    }
-
-    private static DCPermissions dcPerms(String query)
-    {
-        return parse(query).dcPermissions;
-    }
-
-    @Test
-    public void allDcsImplicit() throws Exception
+    @Override
+    public void execute(NodeProbe probe)
     {
-        Assert.assertFalse(dcPerms("CREATE USER u1").restrictsAccess());
+        probe.reloadCidrGroupsCache();
+        probe.output().out.println("Reloaded CIDR groups cache");
     }
 }
diff --git a/src/java/org/apache/cassandra/tools/nodetool/UpdateCIDRGroup.java b/src/java/org/apache/cassandra/tools/nodetool/UpdateCIDRGroup.java
new file mode 100644
index 0000000000..36bef4ed8c
--- /dev/null
+++ b/src/java/org/apache/cassandra/tools/nodetool/UpdateCIDRGroup.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.tools.nodetool;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.auth.AuthKeyspace;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Nodetool command to insert/update a CIDR group and associated mapping in the table {@link AuthKeyspace#CIDR_GROUPS}
+ */
+@Command(name = "updatecidrgroup", description = "Insert/Update a cidr group")
+public class UpdateCIDRGroup extends NodeToolCmd
+{
+    @Arguments(usage = "[<cidrGroup> <cidr> ...]", description = "Requires a cidr group name, followed by one or more CIDRs separated by space")
+    private List<String> args = new ArrayList<>();
+
+    @Override
+    public void execute(NodeProbe probe)
+    {
+        checkArgument(args.size() > 1, "updatecidrgroup command requires a cidr group name and atleast one CIDR");
+
+        String cidrGroupName = args.get(0);
+
+        try
+        {
+            probe.updateCidrGroup(cidrGroupName, new ArrayList<>(args.subList(1, args.size())));
+        }
+        catch (IllegalArgumentException e)
+        {
+            // IllegalArgumentException message going to stdout, so throw different type of exception to land in stderr
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java b/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java
index ece6052a75..e7c9776718 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java
@@ -49,7 +49,8 @@ public class JMXGetterCheckTest extends TestBaseImpl
     "org.apache.cassandra.db:type=StorageService:stopDaemon", // halts the instance, which then causes the JVM to exit
     "org.apache.cassandra.db:type=StorageService:drain", // don't drain, it stops things which can cause other APIs to be unstable as we are in a stopped state
     "org.apache.cassandra.db:type=StorageService:stopGossiping", // if we stop gossip this can cause other issues, so avoid
-    "org.apache.cassandra.db:type=StorageService:resetLocalSchema" // this will fail when there are no other nodes which can serve schema
+    "org.apache.cassandra.db:type=StorageService:resetLocalSchema", // this will fail when there are no other nodes which can serve schema
+    "org.apache.cassandra.db:type=CIDRGroupsMappingManager:loadCidrGroupsCache" // AllowAllCIDRAuthorizer doesn't support this operation, as feature is disabled by default
     );
 
     public static final String JMX_SERVICE_URL_FMT = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi";
diff --git a/test/microbench/org/apache/cassandra/test/microbench/CIDRAuthorizerBench.java b/test/microbench/org/apache/cassandra/test/microbench/CIDRAuthorizerBench.java
new file mode 100644
index 0000000000..57ab736326
--- /dev/null
+++ b/test/microbench/org/apache/cassandra/test/microbench/CIDRAuthorizerBench.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.test.microbench;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.auth.AuthenticatedUser;
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.exceptions.UnauthorizedException;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.annotations.Warmup;
+
+/**
+ * Benchmark CIDR authorizer (enforce mode)
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 200, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1, jvmArgsAppend = { "-Xmx512M", "-Djmh.executor=CUSTOM", "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor" })
+@Threads(4) // make sure this matches the number of _physical_cores_
+@State(Scope.Benchmark)
+public class CIDRAuthorizerBench extends CQLTester
+{
+    static Random random = new Random();
+
+    static Map<String, List<String>> usersList = new HashMap<>();
+    static List<InetAddress> ipList = new ArrayList<>();
+    static Map<String, List<CIDR>> cidrsMapping = new HashMap<>();
+
+    @Setup(Level.Trial)
+    public void setup() throws IOException
+    {
+        CQLTester.setUpClass();
+        CQLTester.requireAuthentication();
+
+        byte[] ipParts = new byte[4];
+        int numEntries = 100;
+
+        while (numEntries > 0)
+        {
+            ipParts[0] = (byte) random.nextInt(256);
+            ipParts[1] = (byte) random.nextInt(256);
+            ipParts[2] = (byte) random.nextInt(256);
+            InetAddress ip = InetAddress.getByAddress(ipParts);
+            ipList.add(ip);
+
+            String cidrGroupName = "cidrGroup" + numEntries;
+            cidrsMapping.put(cidrGroupName, Collections.singletonList(new CIDR(ip, (short)24)));
+
+            String userName = "user" + numEntries;
+            usersList.put(userName, Collections.singletonList(cidrGroupName));
+            numEntries--;
+        }
+
+        AuthTestUtils.createUsersWithCidrAccess(usersList);
+        AuthTestUtils.insertCidrsMappings(cidrsMapping);
+    }
+
+    @TearDown(Level.Trial)
+    public void teardown() throws IOException, ExecutionException, InterruptedException
+    {
+        CQLTester.cleanup();
+    }
+
+    @State(Scope.Thread)
+    public static class StateForValidLogin
+    {
+        InetSocketAddress ipAddr;
+        AuthenticatedUser user;
+
+        @Setup
+        public void setupStateForValidLogin() throws UnknownHostException
+        {
+            int randIndex = random.nextInt(usersList.size());
+
+            ipAddr = new InetSocketAddress(ipList.get(randIndex), 0);
+            user = new AuthenticatedUser("user" + randIndex);
+        }
+    }
+
+    @State(Scope.Thread)
+    public static class StateForInvalidLogin
+    {
+        InetSocketAddress ipAddr;
+        AuthenticatedUser user;
+
+        @Setup
+        public void setupStateForInvalidLogin() throws UnknownHostException
+        {
+            int randIndex = random.nextInt(usersList.size());
+
+            ipAddr = new InetSocketAddress(ipList.get(randIndex), 0);
+            user = new AuthenticatedUser("user" + (randIndex == 0 ? randIndex + 1 : randIndex - 1));
+        }
+    }
+
+    @Benchmark
+    public void benchCidrAuthorizer_ValidLogin(StateForValidLogin state)
+    {
+        state.user.hasAccessFromIp(state.ipAddr);
+    }
+
+    @Benchmark
+    public void benchCidrAuthorizer_InvalidLogin(StateForInvalidLogin state)
+    {
+        try
+        {
+            state.user.hasAccessFromIp(state.ipAddr);
+        }
+        catch (UnauthorizedException e)
+        {
+            // Exception expected for invalid login, do nothing here
+        }
+    }
+}
diff --git a/test/microbench/org/apache/cassandra/test/microbench/LongestMatchingCIDRBench.java b/test/microbench/org/apache/cassandra/test/microbench/LongestMatchingCIDRBench.java
new file mode 100644
index 0000000000..2cb555ed78
--- /dev/null
+++ b/test/microbench/org/apache/cassandra/test/microbench/LongestMatchingCIDRBench.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.test.microbench;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.cassandra.auth.CIDRGroupsMappingTable;
+import org.apache.cassandra.cql3.CIDR;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.annotations.Warmup;
+
+/**
+ * Benchmark algorithm(s) of the Longest matching CIDR for given IP
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 30, time = 2, timeUnit = TimeUnit.SECONDS)
+@Fork(value = 1, jvmArgsAppend = { "-Xmx512M", "-Djmh.executor=CUSTOM", "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor" })
+@Threads(4) // make sure this matches the number of _physical_cores_
+@State(Scope.Benchmark)
+public class LongestMatchingCIDRBench
+{
+    CIDRGroupsMappingTable<String> ipv4CidrGroupsMappingTable;
+    static Random random = new Random();
+
+    static List<String> cidrsToInsert = Arrays.asList("100.90.80.70/14",
+                                                      "110.100.90.80/15",
+                                                      "120.110.100.90/15",
+                                                      "130.120.110.100/15",
+                                                      "140.130.120.110/15",
+                                                      "150.140.130.120/16",
+                                                      "160.150.140.130/16",
+                                                      "170.160.150.140/16",
+                                                      "180.170.160.150/16",
+                                                      "190.180.170.160/16",
+                                                      "200.190.180.170/16",
+                                                      "210.200.190.180/16",
+                                                      "220.210.200.190/17",
+                                                      "230.220.210.200/17",
+                                                      "240.230.220.210/17",
+                                                      "250.240.230.220/18",
+                                                      "255.250.240.230/18");
+
+    static List<String> ipsNotInserted = Arrays.asList("15.15.15.15",
+                                                       "16.16.16.16",
+                                                       "255.255.255.255",
+                                                       "200.0.0.0",
+                                                       "18.18.18.18",
+                                                       "19.19.19.19",
+                                                       "20.20.20.20",
+                                                       "21.21.21.21",
+                                                       "22.22.22.22",
+                                                       "23.23.23.23",
+                                                       "24.24.24.24",
+                                                       "25.25.25.25",
+                                                       "26.26.26.26",
+                                                       "27.27.27.27",
+                                                       "28.28.28.28",
+                                                       "29.29.29.29",
+                                                       "30.30.30.30");
+
+    @Setup(Level.Trial)
+    public void setup()
+    {
+        CIDRGroupsMappingTable.Builder<String> ipv4CidrGroupsMappingTableBuilder = CIDRGroupsMappingTable.builder(false);
+
+        for (String cidr : cidrsToInsert)
+        {
+            ipv4CidrGroupsMappingTableBuilder.add(CIDR.getInstance(cidr), cidr);
+        }
+        ipv4CidrGroupsMappingTable = ipv4CidrGroupsMappingTableBuilder.build();
+    }
+
+    @State(Scope.Thread)
+    public static class StateWithExistingCIDR
+    {
+        InetAddress ipAddr;
+
+        @Setup
+        public void setupStateWithExistingCidr() throws UnknownHostException
+        {
+            ipAddr = InetAddress.getByName(cidrsToInsert.get(random.nextInt(cidrsToInsert.size())).split("/")[0]);
+        }
+    }
+
+    @State(Scope.Thread)
+    public static class StateWithNonExistingCIDR
+    {
+        InetAddress ipAddr;
+
+        @Setup
+        public void setupStateWithNonExistingCidr() throws UnknownHostException
+        {
+            ipAddr = InetAddress.getByName(ipsNotInserted.get(random.nextInt(ipsNotInserted.size())));
+        }
+    }
+
+    @Benchmark
+    public void benchLongestMatchingCidr_CidrExistsCase(StateWithExistingCIDR state)
+    {
+        ipv4CidrGroupsMappingTable.lookupLongestMatchForIP(state.ipAddr);
+    }
+
+    @Benchmark
+    public void benchLongestMatchingCidr_CidrNotExistsCase(StateWithNonExistingCIDR state)
+    {
+        ipv4CidrGroupsMappingTable.lookupLongestMatchForIP(state.ipAddr);
+    }
+}
diff --git a/test/unit/org/apache/cassandra/SchemaLoader.java b/test/unit/org/apache/cassandra/SchemaLoader.java
index f0178d1a2e..f341fb6eb8 100644
--- a/test/unit/org/apache/cassandra/SchemaLoader.java
+++ b/test/unit/org/apache/cassandra/SchemaLoader.java
@@ -29,6 +29,7 @@ import org.apache.cassandra.auth.AuthKeyspace;
 import org.apache.cassandra.auth.AuthSchemaChangeListener;
 import org.apache.cassandra.auth.IAuthenticator;
 import org.apache.cassandra.auth.IAuthorizer;
+import org.apache.cassandra.auth.ICIDRAuthorizer;
 import org.apache.cassandra.auth.INetworkAuthorizer;
 import org.apache.cassandra.auth.IRoleManager;
 import org.apache.cassandra.config.*;
@@ -280,18 +281,21 @@ public class SchemaLoader
         SchemaTestUtil.announceNewKeyspace(KeyspaceMetadata.create(name, params, tables, Views.none(), types, UserFunctions.none()));
     }
 
-    public static void setupAuth(IRoleManager roleManager, IAuthenticator authenticator, IAuthorizer authorizer, INetworkAuthorizer networkAuthorizer)
+    public static void setupAuth(IRoleManager roleManager, IAuthenticator authenticator, IAuthorizer authorizer, INetworkAuthorizer networkAuthorizer,
+                                 ICIDRAuthorizer cidrAuthorizer)
     {
         DatabaseDescriptor.setRoleManager(roleManager);
         DatabaseDescriptor.setAuthenticator(authenticator);
         DatabaseDescriptor.setAuthorizer(authorizer);
         DatabaseDescriptor.setNetworkAuthorizer(networkAuthorizer);
+        DatabaseDescriptor.setCIDRAuthorizer(cidrAuthorizer);
         SchemaTestUtil.announceNewKeyspace(AuthKeyspace.metadata());
         DatabaseDescriptor.getRoleManager().setup();
         DatabaseDescriptor.getAuthenticator().setup();
         DatabaseDescriptor.getInternodeAuthenticator().setupInternode();
         DatabaseDescriptor.getAuthorizer().setup();
         DatabaseDescriptor.getNetworkAuthorizer().setup();
+        DatabaseDescriptor.getCIDRAuthorizer().setup();
         Schema.instance.registerListener(new AuthSchemaChangeListener());
     }
 
diff --git a/test/unit/org/apache/cassandra/auth/AllowAllCIDRAuthorizerTest.java b/test/unit/org/apache/cassandra/auth/AllowAllCIDRAuthorizerTest.java
new file mode 100644
index 0000000000..b5c0e876ee
--- /dev/null
+++ b/test/unit/org/apache/cassandra/auth/AllowAllCIDRAuthorizerTest.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetSocketAddress;
+import java.util.Collections;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.service.ClientState;
+
+import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS;
+import static org.apache.cassandra.auth.AuthKeyspace.CIDR_PERMISSIONS;
+import static org.apache.cassandra.auth.AuthTestUtils.auth;
+import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME;
+
+/**
+ * Tests to verify AllowAllCIDRAuthorizer allows access from any CIDR
+ */
+public class AllowAllCIDRAuthorizerTest extends CQLTester
+{
+    private static void setupSuperUser()
+    {
+        QueryProcessor.executeInternal(String.format("INSERT INTO %s.%s (role, is_superuser, can_login, salted_hash) "
+                                                     + "VALUES ('%s', true, true, '%s')",
+                                                     AUTH_KEYSPACE_NAME,
+                                                     AuthKeyspace.ROLES,
+                                                     CassandraRoleManager.DEFAULT_SUPERUSER_NAME,
+                                                     "xxx"));
+    }
+
+    @BeforeClass
+    public static void defineSchema() throws ConfigurationException
+    {
+        SchemaLoader.prepareServer();
+
+        SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(),
+                               new AuthTestUtils.LocalPasswordAuthenticator(),
+                               new AuthTestUtils.LocalCassandraAuthorizer(),
+                               new AuthTestUtils.LocalCassandraNetworkAuthorizer(),
+                               new AuthTestUtils.LocalAllowAllCIDRAuthorizer());
+        AuthCacheService.initializeAndRegisterCaches();
+        setupSuperUser();
+    }
+
+    @Before
+    public void before()
+    {
+        Keyspace.open(AUTH_KEYSPACE_NAME).getColumnFamilyStore(CIDR_GROUPS).truncateBlocking();
+        Keyspace.open(AUTH_KEYSPACE_NAME).getColumnFamilyStore(CIDR_PERMISSIONS).truncateBlocking();
+    }
+
+    private void testAccessSucceeds(String userName, String ip)
+    {
+        InetSocketAddress ipAddr = new InetSocketAddress(ip, 0);
+
+        AuthenticatedUser user = new AuthenticatedUser(userName);
+        Assert.assertTrue(user.hasAccessFromIp(ipAddr));
+
+        ClientState clientState = ClientState.forExternalCalls(ipAddr);
+        clientState.login(user);
+        clientState.validateLogin(); // expect no exception
+    }
+
+    @Test
+    public void testSuperUser()
+    {
+        testAccessSucceeds(CassandraRoleManager.DEFAULT_SUPERUSER_NAME, "10.20.30.5");
+    }
+
+    @Test
+    public void testUserWithoutCidrRestrictions()
+    {
+        String roleName = "role1";
+        auth("CREATE ROLE %s WITH password = 'password' AND LOGIN = true ",
+             roleName);
+        testAccessSucceeds(roleName, "127.0.0.1");
+    }
+
+    @Test
+    public void testUserWithRestrictingCidrGroups()
+    {
+        String roleName = "role1";
+        auth("CREATE ROLE %s WITH password = 'password' AND LOGIN = true AND ACCESS FROM CIDRS { 'cidrGroup1' }",
+             roleName);
+        testAccessSucceeds(roleName, "12.0.0.1");
+    }
+
+    @Test
+    public void testUserAllowedforAllCidrs()
+    {
+        String roleName = "role1";
+        auth("CREATE ROLE %s WITH password = 'password' AND LOGIN = true AND ACCESS FROM ALL CIDRS",
+             roleName);
+        testAccessSucceeds(roleName, "17.0.0.1");
+    }
+
+    private UntypedResultSet getCidrGroups(String role)
+    {
+        String query = String.format("SELECT %s FROM %s.%s WHERE role = '%s'",
+                                     "cidr_groups",
+                                     AUTH_KEYSPACE_NAME,
+                                     CIDR_PERMISSIONS,
+                                     RoleResource.role(role).getRoleName());
+        return QueryProcessor.executeInternal(query);
+    }
+
+    // Create role command with access from cidrs clause should work even when CIDR authorizer is disabled
+    @Test
+    public void testCreateAndDropRoleWithCidrsClause() throws Throwable
+    {
+        String role = "role1";
+
+        assertEmpty(getCidrGroups(role));
+
+        AuthTestUtils.createUsersWithCidrAccess(Collections.singletonMap(role,
+                                                                         Collections.singletonList("cidrGroup1")));
+        UntypedResultSet results = getCidrGroups(role);
+        Assert.assertEquals(Sets.newHashSet("cidrGroup1"),
+                            Iterables.getOnlyElement(results).getFrozenSet("cidr_groups", UTF8Type.instance));
+
+        AuthTestUtils.auth("DROP ROLE %s", role);
+        assertEmpty(getCidrGroups(role));
+    }
+
+    // Alter role command with access from cidrs clause should work even when CIDR authorizer is disabled
+    @Test
+    public void testAlterAndDropRoleWithCidrsClause() throws Throwable
+    {
+        String role = "role1";
+
+        assertEmpty(getCidrGroups(role));
+
+        auth("CREATE ROLE %s WITH password = 'password' AND LOGIN = true ", role);
+        auth("ALTER ROLE %s WITH ACCESS FROM CIDRS {'%s'}", role, "cidrGroup1");
+
+        UntypedResultSet results = getCidrGroups(role);
+        Assert.assertEquals(Sets.newHashSet("cidrGroup1"),
+                            Iterables.getOnlyElement(results).getFrozenSet("cidr_groups", UTF8Type.instance));
+
+        AuthTestUtils.auth("DROP ROLE %s", role);
+        assertEmpty(getCidrGroups(role));
+    }
+}
diff --git a/test/unit/org/apache/cassandra/auth/AuthTestUtils.java b/test/unit/org/apache/cassandra/auth/AuthTestUtils.java
index e3ef505be9..610832ffb4 100644
--- a/test/unit/org/apache/cassandra/auth/AuthTestUtils.java
+++ b/test/unit/org/apache/cassandra/auth/AuthTestUtils.java
@@ -18,6 +18,8 @@
 
 package org.apache.cassandra.auth;
 
+import java.util.List;
+import java.util.Map;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.InetAddress;
@@ -32,20 +34,31 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.TimeoutException;
 
 import org.apache.cassandra.auth.jmx.AuthorizationProxy;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.CQLStatement;
 import org.apache.cassandra.cql3.QueryOptions;
 import org.apache.cassandra.cql3.QueryProcessor;
 import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.cql3.statements.AlterRoleStatement;
+import org.apache.cassandra.cql3.statements.AuthenticationStatement;
 import org.apache.cassandra.cql3.statements.BatchStatement;
+import org.apache.cassandra.cql3.statements.CreateRoleStatement;
+import org.apache.cassandra.cql3.statements.DropRoleStatement;
 import org.apache.cassandra.cql3.statements.SelectStatement;
 import org.apache.cassandra.db.ColumnFamilyStore;
 import org.apache.cassandra.db.ConsistencyLevel;
 import org.apache.cassandra.db.Keyspace;
 import org.apache.cassandra.exceptions.RequestExecutionException;
 import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.service.QueryState;
 import org.apache.cassandra.service.StorageService;
 import org.apache.cassandra.transport.messages.ResultMessage;
 
+import static java.lang.String.format;
+import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS;
+import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME;
 import static org.junit.Assert.assertNotNull;
 
 
@@ -133,6 +146,84 @@ public class AuthTestUtils
         }
     }
 
+    public static class LocalCIDRGroupsMappingManager extends CIDRGroupsMappingManager
+    {
+        @Override
+        ResultMessage.Rows select(SelectStatement statement, QueryOptions options)
+        {
+            return statement.executeLocally(QueryState.forInternalCalls(), options);
+        }
+
+        @Override
+        UntypedResultSet process(String query, ConsistencyLevel cl)
+        {
+            return QueryProcessor.executeInternal(query);
+        }
+
+        public static String getCidrTuplesSet(List<CIDR> cidrs)
+        {
+            return getCidrTuplesSetString(cidrs);
+        }
+    }
+
+    public static class LocalCIDRPermissionsManager extends CIDRPermissionsManager
+    {
+        @Override
+        public ResultMessage.Rows select(SelectStatement statement, QueryOptions options)
+        {
+            return statement.executeLocally(QueryState.forInternalCalls(), options);
+        }
+
+        @Override
+        public UntypedResultSet process(String query, ConsistencyLevel cl)
+        {
+            return QueryProcessor.executeInternal(query);
+        }
+    }
+
+    public static class LocalCassandraCIDRAuthorizer extends CassandraCIDRAuthorizer
+    {
+        CIDRAuthorizerMode cidrAuthorizerMode;
+
+        public LocalCassandraCIDRAuthorizer()
+        {
+            cidrAuthorizerMode = CIDRAuthorizerMode.ENFORCE;
+        }
+
+        public LocalCassandraCIDRAuthorizer(CIDRAuthorizerMode mode)
+        {
+            cidrAuthorizerMode = mode;
+        }
+
+        @Override
+        protected void createManagers()
+        {
+            cidrPermissionsManager = new LocalCIDRPermissionsManager();
+            cidrGroupsMappingManager = new LocalCIDRGroupsMappingManager();
+        }
+
+        @Override
+        protected boolean isMonitorMode()
+        {
+            return cidrAuthorizerMode == CIDRAuthorizerMode.MONITOR;
+        }
+
+        CIDRPermissionsCache getCidrPermissionsCache()
+        {
+            return cidrPermissionsCache;
+        }
+    }
+
+    public static class LocalAllowAllCIDRAuthorizer extends AllowAllCIDRAuthorizer
+    {
+        @Override
+        protected void createManagers()
+        {
+            cidrPermissionsManager = new LocalCIDRPermissionsManager();
+            cidrGroupsMappingManager = new LocalCIDRGroupsMappingManager();
+        }
+    }
+
     public static class LocalPasswordAuthenticator extends PasswordAuthenticator
     {
         @Override
@@ -170,6 +261,13 @@ public class AuthTestUtils
         return networkPemissionsTable.metric.readLatency.latency.getCount();
     }
 
+    public static long getCidrPermissionsReadCount()
+    {
+        ColumnFamilyStore cidrPemissionsTable =
+        Keyspace.open(SchemaConstants.AUTH_KEYSPACE_NAME).getColumnFamilyStore(AuthKeyspace.CIDR_PERMISSIONS);
+        return cidrPemissionsTable.metric.readLatency.latency.getCount();
+    }
+
     public static long getRolePermissionsReadCount()
     {
         ColumnFamilyStore rolesPemissionsTable =
@@ -192,6 +290,59 @@ public class AuthTestUtils
         return roleOptions;
     }
 
+    private static ClientState getClientState()
+    {
+        ClientState state = ClientState.forInternalCalls();
+        state.login(new AuthenticatedUser(CassandraRoleManager.DEFAULT_SUPERUSER_NAME));
+        return state;
+    }
+
+    public static AuthenticationStatement authWithoutInvalidate(String query, Object... args)
+    {
+        CQLStatement statement = QueryProcessor.parseStatement(String.format(query, args)).prepare(ClientState.forInternalCalls());
+        assert statement instanceof CreateRoleStatement
+               || statement instanceof AlterRoleStatement
+               || statement instanceof DropRoleStatement;
+        AuthenticationStatement authStmt = (AuthenticationStatement) statement;
+
+        authStmt.execute(getClientState());
+
+        return authStmt;
+    }
+
+    public static AuthenticationStatement auth(String query, Object... args)
+    {
+        AuthenticationStatement authStmt = authWithoutInvalidate(query, args);
+
+        // invalidate roles cache so that any changes to the underlying roles are picked up
+        Roles.cache.invalidate();
+
+        return authStmt;
+    }
+
+    public static void createUsersWithCidrAccess(Map<String, List<String>> userToCidrPermsMapping)
+    {
+        for (Map.Entry<String, List<String>> userMapping : userToCidrPermsMapping.entrySet())
+        {
+            authWithoutInvalidate(
+                "CREATE ROLE %s WITH password = 'password' AND LOGIN = true AND ACCESS FROM CIDRS {'%s'}",
+                 userMapping.getKey(), String.join("', '", userMapping.getValue()));
+        }
+        Roles.cache.invalidate();
+    }
+
+    public static void insertCidrsMappings(Map<String, List<CIDR>> cidrsMapping)
+    {
+        for (Map.Entry<String, List<CIDR>> cidrMapping : cidrsMapping.entrySet())
+        {
+            QueryProcessor.executeInternal(format("insert into %s.%s(cidr_group, cidrs) values('%s', %s );",
+                                                  AUTH_KEYSPACE_NAME, CIDR_GROUPS, cidrMapping.getKey(),
+                                                  AuthTestUtils.LocalCIDRGroupsMappingManager.getCidrTuplesSet(
+                                                  cidrMapping.getValue())));
+        }
+        DatabaseDescriptor.getCIDRAuthorizer().loadCidrGroupsCache(); // update cache with CIDRs inserted above
+    }
+
     // mTLS authenticators related utility methods
     public static InetAddress getMockInetAddress() throws UnknownHostException
     {
diff --git a/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingIntervalTreeTest.java b/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingIntervalTreeTest.java
new file mode 100644
index 0000000000..699aa2cd2c
--- /dev/null
+++ b/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingIntervalTreeTest.java
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.Test;
+
+import org.apache.cassandra.auth.CIDRGroupsMappingIntervalTree.IPIntervalNode;
+import org.apache.cassandra.cql3.CIDR;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test the internal data structure used for the interval tree.
+ * The test cases for CIDRGroupsMappingTable functionality can be found at {@link CIDRGroupsMappingTableTest}
+ */
+public class CIDRGroupsMappingIntervalTreeTest
+{
+    private IPIntervalNode<Void> createInternalNode(String parentCidr, IPIntervalNode<Void>[] children)
+    {
+        return new IPIntervalNode<>(CIDR.getInstance(parentCidr), null, children);
+    }
+
+    @SuppressWarnings("unchecked")
+    private IPIntervalNode<Void>[] createLeaves(List<String> cidrs)
+    {
+        return cidrs.stream().map(this::createLeafNode).toArray(IPIntervalNode[]::new);
+    }
+
+    private IPIntervalNode<Void> createLeafNode(String cidr)
+    {
+        return new IPIntervalNode<>(CIDR.getInstance(cidr), null, null);
+    }
+
+    private List<CIDR> toCidrs(IPIntervalNode<Void>[] nodes)
+    {
+        return Arrays.stream(nodes).map(IPIntervalNode::cidr).collect(Collectors.toList());
+    }
+
+    @Test
+    public void testInternalNodeAllLeft()
+    {
+        List<String> cidrs = Arrays.asList("10.0.0.1/24",
+                                           "10.0.1.1/24",
+                                           "10.0.2.1/24",
+                                           "10.0.3.1/24");
+        String parentCidr = "11.0.0.1/30";
+
+        IPIntervalNode<Void>[] children = createLeaves(cidrs);
+        IPIntervalNode<Void> internalNode = createInternalNode(parentCidr, children);
+
+        assertThat(internalNode.left()).isEqualTo(children);
+        assertThat(internalNode.right()).isNull();
+    }
+
+    @Test
+    public void testInternalNodeAllRight()
+    {
+        List<String> cidrs = Arrays.asList("10.0.0.1/24",
+                                           "10.0.1.1/24",
+                                           "10.0.2.1/24",
+                                           "10.0.3.1/24");
+        String parentCidr = "9.0.0.1/30";
+
+        IPIntervalNode<Void>[] children = createLeaves(cidrs);
+        IPIntervalNode<Void> internalNode = createInternalNode(parentCidr, children);
+
+        assertThat(internalNode.left()).isNull();
+        assertThat(internalNode.right()).isEqualTo(children);
+    }
+
+    @Test
+    public void testInternalNodeNoOverlapWithParent() // parent node does not overlap with closest
+    {
+        List<String> cidrs = Arrays.asList("10.0.0.1/24",
+                                           "10.0.1.1/24",
+                                           "10.0.3.1/24",
+                                           "10.0.4.1/24");
+        String parentCidr = "10.0.2.1/30";
+
+        IPIntervalNode<Void>[] children = createLeaves(cidrs);
+        IPIntervalNode<Void> internalNode = new IPIntervalNode<>(CIDR.getInstance(parentCidr), null, children);
+
+        assertThat(toCidrs(internalNode.left())).describedAs("Check left nodes")
+                                                .isEqualTo(toCidrs(createLeaves(Arrays.asList("10.0.0.1/24",
+                                                                                              "10.0.1.1/24"))));
+
+        assertThat(toCidrs(internalNode.right())).describedAs("Check right nodes")
+                                                 .isEqualTo(toCidrs(createLeaves(Arrays.asList("10.0.3.1/24",
+                                                                                               "10.0.4.1/24"))));
+    }
+
+    @Test
+    public void testInternalNodeNoOverlapWithParentIpv6() // parent node does not overlap with closest
+    {
+        List<String> cidrs = Arrays.asList("aaaa::0001:ffff/112",
+                                           "bbbb::0001:ffff/112",
+                                           "dddd::0001:ffff/112",
+                                           "eeee::0001:ffff/112");
+        String parentCidr = "cccc::0001:ffff/113";
+
+        IPIntervalNode<Void>[] children = createLeaves(cidrs);
+        IPIntervalNode<Void> internalNode = new IPIntervalNode<>(CIDR.getInstance(parentCidr), null, children);
+
+        assertThat(toCidrs(internalNode.left())).describedAs("Check left nodes")
+                                                .isEqualTo(toCidrs(createLeaves(Arrays.asList("aaaa::0001:ffff/112",
+                                                                                              "bbbb::0001:ffff/112"))));
+
+        assertThat(toCidrs(internalNode.right())).describedAs("Check right nodes")
+                                                 .isEqualTo(toCidrs(createLeaves(
+                                                 Arrays.asList("dddd::0001:ffff/112", "eeee::0001:ffff/112"))));
+    }
+
+    @Test
+    public void testInternalNodeOverlapWithParent() // parent node overlaps with closest
+    {
+        List<String> cidrs = Arrays.asList("10.0.0.1/24",
+                                           "10.0.1.1/24",
+                                           "10.0.2.1/24",
+                                           "10.0.3.1/24");
+        String parentCidr = "10.0.2.1/25"; // overlaps with "10.0.2.1/24"
+
+        IPIntervalNode<Void>[] children = createLeaves(cidrs);
+        IPIntervalNode<Void> internalNode = new IPIntervalNode<>(CIDR.getInstance(parentCidr), null, children);
+
+        // The overlapping node "10.0.2.1/24" should be included in both left and right
+        assertThat(toCidrs(internalNode.left())).describedAs("Check left nodes")
+                                                .isEqualTo(toCidrs(createLeaves(Arrays.asList("10.0.0.1/24",
+                                                                                              "10.0.1.1/24",
+                                                                                              "10.0.2.1/24"))));
+
+        assertThat(toCidrs(internalNode.right())).describedAs("Check right nodes")
+                                                 .isEqualTo(toCidrs(createLeaves(Arrays.asList("10.0.2.1/24",
+                                                                                               "10.0.3.1/24"))));
+    }
+
+    @Test
+    public void testInternalNodeOverlapWithParentIpv6() // parent node overlaps with closest - IPv6
+    {
+        List<String> cidrs = Arrays.asList("aaaa::0001:ffff/112",
+                                           "bbbb::0001:ffff/112",
+                                           "cccc::0001:ffff/112",
+                                           "dddd::0001:ffff/112",
+                                           "eeee::0001:ffff/112");
+        String parentCidr = "cccc::0001:ffff/113"; // overlaps with "cccc::0001:ffff/112"
+
+        IPIntervalNode<Void>[] children = createLeaves(cidrs);
+        IPIntervalNode<Void> internalNode = new IPIntervalNode<>(CIDR.getInstance(parentCidr), null, children);
+
+        // The overlapping node "10.0.2.1/24" should be included in both left and right
+        assertThat(toCidrs(internalNode.left())).describedAs("Check left nodes")
+                                                .isEqualTo(toCidrs(createLeaves(Arrays.asList("aaaa::0001:ffff/112",
+                                                                                              "bbbb::0001:ffff/112",
+                                                                                              "cccc::0001:ffff/112"))));
+
+        assertThat(toCidrs(internalNode.right())).describedAs("Check right nodes")
+                                                 .isEqualTo(toCidrs(createLeaves(
+                                                 Arrays.asList("cccc::0001:ffff/112",
+                                                               "dddd::0001:ffff/112",
+                                                               "eeee::0001:ffff/112"))));
+    }
+
+    @Test
+    public void testSearchReturnsExactMatch() throws UnknownHostException
+    {
+        List<String> cidrs = Arrays.asList("10.0.0.1/24",
+                                           "10.0.1.1/24",
+                                           "10.0.2.1/24",
+                                           "10.0.3.1/24");
+
+        IPIntervalNode<Void>[] nodes = createLeaves(cidrs);
+
+        // exhaust all IPs covered by the CIDRs list
+        for (int i = 0; i < cidrs.size(); i++)
+        {
+            for (int j = 1; j < 256; j++)
+            {
+                String ip = String.format("10.0.%d.%d", i, j);
+                int index = IPIntervalNode.binarySearchNodesIndex(nodes, InetAddress.getByName(ip));
+                assertThat(index).isEqualTo(i);
+            }
+        }
+    }
+
+    @Test
+    public void testSearchReturnsClosest() throws UnknownHostException
+    {
+        List<String> cidrs = Arrays.asList("10.0.0.1/24",
+                                           "10.0.2.1/24",
+                                           "10.0.4.1/24",
+                                           "10.0.6.1/24",
+                                           "10.0.8.1/24"
+        );
+
+        IPIntervalNode<Void>[] nodes = createLeaves(cidrs);
+
+        for (int i = 1, expected = 0; i < 10; i++)
+        {
+            if (i % 2 == 0) // skip the even values as they are in CIDRs
+                continue;
+
+            for (int j = 1; j < 256; j++)
+            {
+                String ip = String.format("10.0.%d.%d", i, j);
+                int index = IPIntervalNode.binarySearchNodesIndex(nodes, InetAddress.getByName(ip));
+                assertThat(index).describedAs("The closest node is left to the ip").isEqualTo(expected);
+            }
+            expected++;
+        }
+    }
+}
diff --git a/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingManagerTest.java b/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingManagerTest.java
new file mode 100644
index 0000000000..a3a899d65d
--- /dev/null
+++ b/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingManagerTest.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.exceptions.ConfigurationException;
+
+import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+
+public class CIDRGroupsMappingManagerTest
+{
+    CIDRGroupsMappingManager cidrGroupsMappingManager;
+
+    private static void setupSuperUser()
+    {
+        QueryProcessor.executeInternal(String.format("INSERT INTO %s.%s (role, is_superuser, can_login, salted_hash) "
+                                                     + "VALUES ('%s', true, true, '%s')",
+                                                     AUTH_KEYSPACE_NAME,
+                                                     AuthKeyspace.ROLES,
+                                                     CassandraRoleManager.DEFAULT_SUPERUSER_NAME,
+                                                     "xxx"));
+    }
+
+    @BeforeClass
+    public static void defineSchema() throws ConfigurationException
+    {
+        SchemaLoader.prepareServer();
+
+        SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(),
+                               new AuthTestUtils.LocalPasswordAuthenticator(),
+                               new AuthTestUtils.LocalCassandraAuthorizer(),
+                               new AuthTestUtils.LocalCassandraNetworkAuthorizer(),
+                               new AuthTestUtils.LocalCassandraCIDRAuthorizer());
+        AuthCacheService.initializeAndRegisterCaches();
+        setupSuperUser();
+    }
+
+    @Before
+    public void setup()
+    {
+        cidrGroupsMappingManager = new AuthTestUtils.LocalCIDRGroupsMappingManager();
+        cidrGroupsMappingManager.setup();
+
+        Map<String, List<CIDR>> cidrsMapping = new HashMap<String, List<CIDR>>()
+        {{
+            put("cidrGroup1", Collections.singletonList(CIDR.getInstance("10.20.30.5/24")));
+            put("cidrGroup2", Arrays.asList(CIDR.getInstance("20.30.40.6/16"), CIDR.getInstance("30.40.50.6/8")));
+            put("cidrGroup3", Arrays.asList(CIDR.getInstance("40.50.60.7/32"), CIDR.getInstance("50.60.70.80/10"), CIDR.getInstance("60.70.80.90/22")));
+        }};
+
+        AuthTestUtils.insertCidrsMappings(cidrsMapping);
+    }
+
+    @Test
+    public void testGetCidrsOfCidrGroupAsStrings()
+    {
+        Set<String> cidrs = cidrGroupsMappingManager.getCidrsOfCidrGroupAsStrings("cidrGroup2");
+        assertEquals(ImmutableSet.of("20.30.0.0/16", "30.0.0.0/8"), cidrs);
+    }
+
+    @Test
+    public void testUpdateCidrGroup()
+    {
+        cidrGroupsMappingManager.updateCidrGroup("cidrGroup3",
+                                                 Arrays.asList("140.50.60.7/32", "150.60.70.80/10"));
+        assertEquals(ImmutableSet.of("150.0.0.0/10", "140.50.60.7/32"),
+                     cidrGroupsMappingManager.getCidrsOfCidrGroupAsStrings("cidrGroup3"));
+    }
+
+    @Test
+    public void testDropCidrGroupForExistingCidrGroup()
+    {
+        cidrGroupsMappingManager.dropCidrGroup("cidrGroup1");
+        assertEquals(2, cidrGroupsMappingManager.getAvailableCidrGroups().size());
+    }
+
+    @Test
+    public void testDropCidrGroupForNonExistingCidrGroup()
+    {
+        assertThatThrownBy(() -> cidrGroupsMappingManager.dropCidrGroup("cidrGroup10"))
+        .isInstanceOf(RuntimeException.class)
+        .hasMessage("CIDR group 'cidrGroup10' doesn't exists");
+    }
+
+    @Test
+    public void testDropCidrGroupIfExistsForExistingCidrGroup()
+    {
+        cidrGroupsMappingManager.dropCidrGroupIfExists("cidrGroup1");
+        assertEquals(2, cidrGroupsMappingManager.getAvailableCidrGroups().size());
+    }
+
+    @Test
+    public void testDropCidrGroupIfExistsForNonExistingCidrGroup()
+    {
+        // expect no exception
+        cidrGroupsMappingManager.dropCidrGroupIfExists("cidrGroup10");
+    }
+
+    @Test
+    public void testRecreateCidrGroupsMapping()
+    {
+        assertEquals(3, cidrGroupsMappingManager.getAvailableCidrGroups().size());
+        assertEquals(ImmutableSet.of("10.20.30.0/24"),
+                     cidrGroupsMappingManager.getCidrsOfCidrGroupAsStrings("cidrGroup1"));
+        assertEquals(ImmutableSet.of("20.30.0.0/16", "30.0.0.0/8"),
+                     cidrGroupsMappingManager.getCidrsOfCidrGroupAsStrings("cidrGroup2"));
+        assertEquals(ImmutableSet.of("40.50.60.7/32", "50.0.0.0/10", "60.70.80.0/22"),
+                     cidrGroupsMappingManager.getCidrsOfCidrGroupAsStrings("cidrGroup3"));
+
+        // load table with new mappings
+        Map<String, List<String>> cidrsMapping = new HashMap<String, List<String>>()
+        {{
+            put("cidrGroup11", Collections.singletonList("100.20.30.5/24"));
+            put("cidrGroup12", Arrays.asList("120.30.40.6/16", "130.40.50.6/8"));
+        }};
+        cidrGroupsMappingManager.recreateCidrGroupsMapping(cidrsMapping);
+
+        assertEquals(2, cidrGroupsMappingManager.getAvailableCidrGroups().size());
+        assertEquals(ImmutableSet.of("100.20.30.0/24"),
+                     cidrGroupsMappingManager.getCidrsOfCidrGroupAsStrings("cidrGroup11"));
+        assertEquals(ImmutableSet.of("120.30.0.0/16", "130.0.0.0/8"),
+                     cidrGroupsMappingManager.getCidrsOfCidrGroupAsStrings("cidrGroup12"));
+
+        // ensure stale cidr groups are deleted
+        assertEquals(Collections.emptySet(),
+                     cidrGroupsMappingManager.getCidrsOfCidrGroupAsStrings("cidrGroup1"));
+        assertEquals(Collections.emptySet(),
+                     cidrGroupsMappingManager.getCidrsOfCidrGroupAsStrings("cidrGroup2"));
+        assertEquals(Collections.emptySet(),
+                     cidrGroupsMappingManager.getCidrsOfCidrGroupAsStrings("cidrGroup3"));
+    }
+}
\ No newline at end of file
diff --git a/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingTableTest.java b/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingTableTest.java
new file mode 100644
index 0000000000..9353d96fba
--- /dev/null
+++ b/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingTableTest.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.Sets;
+import org.junit.Test;
+
+import org.apache.cassandra.cql3.CIDR;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class CIDRGroupsMappingTableTest
+{
+    private final CIDRGroupsMappingTable.Builder<String> ipv4CidrGroupsMappingTableBuilder;
+    private final CIDRGroupsMappingTable.Builder<String> ipv6CidrGroupsMappingTableBuilder;
+
+    public CIDRGroupsMappingTableTest()
+    {
+        ipv4CidrGroupsMappingTableBuilder = CIDRGroupsMappingTable.builder(false);
+        ipv6CidrGroupsMappingTableBuilder = CIDRGroupsMappingTable.builder(true);
+    }
+
+    private CIDRGroupsMappingTable<String> buildCidrGroupsMappingTable(List<String> cidrs,
+                                                                       CIDRGroupsMappingTable.Builder<String> cidrGroupsMappingTableBuilder)
+    {
+        for (String cidr : cidrs)
+        {
+            cidrGroupsMappingTableBuilder.add(CIDR.getInstance(cidr), cidr);
+        }
+
+        return cidrGroupsMappingTableBuilder.build();
+    }
+
+    private Set<String> longestMatchForIP(CIDRGroupsMappingTable<String> cidrGroupsMappingTable, String ip)
+    {
+        try
+        {
+            InetAddress ipAddr = InetAddress.getByName(ip);
+            return cidrGroupsMappingTable.lookupLongestMatchForIP(ipAddr);
+        }
+        catch (UnknownHostException e)
+        {
+            throw new IllegalArgumentException(String.format("%s is not a valid IP String", ip));
+        }
+    }
+
+    // expects one and single one result
+    private String singleMatchForIp(CIDRGroupsMappingTable<String> cidrGroupsMappingTable, String ip)
+    {
+        return longestMatchForIP(cidrGroupsMappingTable, ip).iterator().next();
+    }
+
+    private String ip(String cidr)
+    {
+        return cidr.split("/")[0];
+    }
+
+    @Test
+    public void insertIpv4CIDRs()
+    {
+        List<String> validCIDRs = Arrays.asList(
+        "128.10.120.2/10",
+        "128.20.120.2/20",
+        "0.0.0.0/0",
+        "10.1.1.2/10",
+        "255.0.0.0/8"
+        );
+
+        CIDRGroupsMappingTable<String> ipv4CidrGroupsMappingTable =
+            buildCidrGroupsMappingTable(validCIDRs, ipv4CidrGroupsMappingTableBuilder);
+
+        for (String cidr : validCIDRs)
+        {
+            assertThat(singleMatchForIp(ipv4CidrGroupsMappingTable, ip(cidr))).isEqualTo(cidr);
+        }
+    }
+
+    private void testIpv6CIDRs()
+    {
+        List<String> validCIDRs = Arrays.asList("2001:0db8:0:0:0:0:1234:5678/127",
+                                                "2001:0db8:3333:4444:CCCC:DDDD:EEEE:FFFF/128",
+                                                "2001:0db8:3333:4444:5555:6666:7777:8800/120",
+                                                "2001:0db8::/30",
+                                                "::1234:5678/40",
+                                                "f000::/4",
+                                                "ffff::/16"
+        );
+
+        Collections.shuffle(validCIDRs);
+
+        CIDRGroupsMappingTable<String> ipv6CidrGroupsMappingTable =
+            buildCidrGroupsMappingTable(validCIDRs, ipv6CidrGroupsMappingTableBuilder);
+
+        for (String cidr : validCIDRs)
+        {
+            assertThat(singleMatchForIp(ipv6CidrGroupsMappingTable, ip(cidr))).isEqualTo(cidr);
+        }
+
+        assertThat(longestMatchForIP(ipv6CidrGroupsMappingTable, "0:0:0100::")).isNull();
+        assertThat(longestMatchForIP(ipv6CidrGroupsMappingTable, "1001::5678")).isNull();
+        assertThat(singleMatchForIp(ipv6CidrGroupsMappingTable, "ffff::5678")).isEqualTo("ffff::/16");
+
+        assertThat(singleMatchForIp(ipv6CidrGroupsMappingTable, "2001:0db8:3333:4444:5555::"))
+        .isEqualTo("2001:0db8::/30");
+        assertThat(singleMatchForIp(ipv6CidrGroupsMappingTable, "2001:0db8:3333:4444:CCCC::"))
+        .isEqualTo("2001:0db8::/30");
+        assertThat(singleMatchForIp(ipv6CidrGroupsMappingTable, "2001:0db8:3333:4444:5555:6666:7777:8822"))
+        .isEqualTo("2001:0db8:3333:4444:5555:6666:7777:8800/120");
+        assertThat(singleMatchForIp(ipv6CidrGroupsMappingTable, "2001:0db8:3333:4444:5555:6666:7777:8282"))
+        .isEqualTo("2001:0db8::/30");
+        assertThat(singleMatchForIp(ipv6CidrGroupsMappingTable, "2001:0db8::1234:5679"))
+        .isEqualTo("2001:0db8:0:0:0:0:1234:5678/127");
+        assertThat(singleMatchForIp(ipv6CidrGroupsMappingTable, "2001:0db8::1234:5698"))
+        .isEqualTo("2001:0db8::/30");
+    }
+
+    @Test
+    public void testIpv6CIDRsInRandomOrder()
+    {
+        for (int i = 0; i < 20; i++)
+            testIpv6CIDRs();
+    }
+
+    @Test
+    public void ipv4MappedCIDRsInIpv6Tree()
+    {
+        // IPv4 mapped IPv6 gets converted to IPv4
+        ipv6CidrGroupsMappingTableBuilder.add(CIDR.getInstance("0:0:0:0:0:ffff:192.1.56.10/96"),
+                                              "0:0:0:0:0:ffff:192.1.56.10/96");
+        assertThatThrownBy(ipv6CidrGroupsMappingTableBuilder::build)
+        .hasMessage("Invalid CIDR format, expecting IPv6, received IPv4")
+        .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void duplicateKeys()
+    {
+        List<String> validCIDRs = Arrays.asList("0:0:0:0:0:ffff:192.1.56.10/100", "::ffff:192.1.56.10/100");
+        CIDRGroupsMappingTable<String> ipv4CidrGroupsMappingTable =
+            buildCidrGroupsMappingTable(validCIDRs, ipv4CidrGroupsMappingTableBuilder);
+
+        Set<String> result = longestMatchForIP(ipv4CidrGroupsMappingTable, "0:0:0:0:0:ffff:192.1.56.10");
+        Set<String> expected = Sets.newHashSet(validCIDRs);
+        assertThat(result).isEqualTo(expected);
+    }
+
+    @Test
+    public void withOverlappingCIDRs()
+    {
+        List<String> validCIDRs = Arrays.asList("127.0.0.0/24", "127.0.1.0/24", "0.0.0.0/24");
+        CIDRGroupsMappingTable<String> ipv4CidrGroupsMappingTable =
+            buildCidrGroupsMappingTable(validCIDRs, ipv4CidrGroupsMappingTableBuilder);
+
+        assertThat(longestMatchForIP(ipv4CidrGroupsMappingTable, "127.1.1.5")).isNull();
+        assertThat(singleMatchForIp(ipv4CidrGroupsMappingTable, "127.0.0.5")).isEqualTo("127.0.0.0/24");
+        assertThat(singleMatchForIp(ipv4CidrGroupsMappingTable, "127.0.1.5")).isEqualTo("127.0.1.0/24");
+        assertThat(singleMatchForIp(ipv4CidrGroupsMappingTable, "0.0.0.5")).isEqualTo("0.0.0.0/24");
+        assertThat(longestMatchForIP(ipv4CidrGroupsMappingTable, "0.0.1.5")).isNull();
+    }
+
+    @Test
+    public void withNetMaskZero()
+    {
+        List<String> validCIDRs = Arrays.asList("0.0.0.0/0",
+                                                "127.0.0.0/24",
+                                                "127.0.0.112/26",
+                                                "112.0.0.0/6");
+        CIDRGroupsMappingTable<String> ipv4CidrGroupsMappingTable =
+            buildCidrGroupsMappingTable(validCIDRs, ipv4CidrGroupsMappingTableBuilder);
+        assertThat(singleMatchForIp(ipv4CidrGroupsMappingTable, "127.1.1.5")).isEqualTo("0.0.0.0/0");
+        assertThat(singleMatchForIp(ipv4CidrGroupsMappingTable, "127.0.0.114")).isEqualTo("127.0.0.112/26");
+        assertThat(singleMatchForIp(ipv4CidrGroupsMappingTable, "113.0.0.5")).isEqualTo("112.0.0.0/6");
+        assertThat(singleMatchForIp(ipv4CidrGroupsMappingTable, "127.0.0.5")).isEqualTo("127.0.0.0/24");
+        assertThat(singleMatchForIp(ipv4CidrGroupsMappingTable, "120.0.0.5")).isEqualTo("0.0.0.0/0");
+        assertThat(singleMatchForIp(ipv4CidrGroupsMappingTable, "127.0.0.64")).isEqualTo("127.0.0.112/26");
+    }
+
+    @Test
+    public void testLongestMatchWithInvalidIP()
+    {
+        CIDRGroupsMappingTable<String> ipv4CidrGroupsMappingTable =
+            buildCidrGroupsMappingTable(Collections.singletonList("20.20.20.20/20"), ipv4CidrGroupsMappingTableBuilder);
+        assertThatThrownBy(() -> longestMatchForIP(ipv4CidrGroupsMappingTable, "2000.20"))
+        .hasMessage("2000.20 is not a valid IP String")
+        .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testEmptyCidrsList()
+    {
+        CIDRGroupsMappingTable<String> ipv4CidrGroupsMappingTable =
+            buildCidrGroupsMappingTable(Collections.emptyList(), ipv4CidrGroupsMappingTableBuilder);
+        Set<String> cidrGroups = longestMatchForIP(ipv4CidrGroupsMappingTable, "20.20.20.20");
+        assertThat(cidrGroups).isEmpty();
+    }
+}
diff --git a/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerEnforceModeTest.java b/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerEnforceModeTest.java
new file mode 100644
index 0000000000..ce0ea16519
--- /dev/null
+++ b/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerEnforceModeTest.java
@@ -0,0 +1,300 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.config.Config;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.config.ParameterizedClass;
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.exceptions.UnauthorizedException;
+import org.apache.cassandra.service.ClientState;
+
+import static java.lang.String.format;
+import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS;
+import static org.apache.cassandra.auth.AuthKeyspace.CIDR_PERMISSIONS;
+import static org.apache.cassandra.auth.AuthTestUtils.auth;
+import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+public class CassandraCIDRAuthorizerEnforceModeTest extends CQLTester
+{
+    private static final AuthTestUtils.LocalCassandraCIDRAuthorizer cidrAuthorizer = new AuthTestUtils.LocalCassandraCIDRAuthorizer();
+
+    private static void setupSuperUser()
+    {
+        QueryProcessor.executeInternal(String.format("INSERT INTO %s.%s (role, is_superuser, can_login, salted_hash) "
+                                                     + "VALUES ('%s', true, true, '%s')",
+                                                     AUTH_KEYSPACE_NAME,
+                                                     AuthKeyspace.ROLES,
+                                                     CassandraRoleManager.DEFAULT_SUPERUSER_NAME,
+                                                     "xxx"));
+    }
+
+    @BeforeClass
+    public static void defineSchema() throws ConfigurationException
+    {
+        SchemaLoader.prepareServer();
+
+        SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(),
+                               new AuthTestUtils.LocalPasswordAuthenticator(),
+                               new AuthTestUtils.LocalCassandraAuthorizer(),
+                               new AuthTestUtils.LocalCassandraNetworkAuthorizer(),
+                               cidrAuthorizer);
+        AuthCacheService.initializeAndRegisterCaches();
+        setupSuperUser();
+    }
+
+    @Before
+    public void clear()
+    {
+        Keyspace.open(AUTH_KEYSPACE_NAME).getColumnFamilyStore(CIDR_GROUPS).truncateBlocking();
+        Keyspace.open(AUTH_KEYSPACE_NAME).getColumnFamilyStore(CIDR_PERMISSIONS).truncateBlocking();
+        cidrAuthorizer.getCidrPermissionsCache().invalidate();
+    }
+
+    private void testValidCidrAccess(String userName, String ip)
+    {
+        InetSocketAddress ipAddr = new InetSocketAddress(ip, 0);
+
+        AuthenticatedUser user = new AuthenticatedUser(userName);
+        Assert.assertTrue(user.hasAccessFromIp(ipAddr));
+
+        ClientState clientState = ClientState.forExternalCalls(ipAddr);
+        clientState.login(user);
+        clientState.validateLogin(); // expect no exception
+    }
+
+    private void testInvalidCidrAccess(String userName, String ip)
+    {
+        InetSocketAddress ipAddr = new InetSocketAddress(ip, 0);
+
+        AuthenticatedUser user = new AuthenticatedUser(userName);
+        Assert.assertFalse(user.hasAccessFromIp(ipAddr));
+    }
+
+    private void testInvalidCidrLogin(String userName, String ip)
+    {
+        ClientState clientState = ClientState.forExternalCalls(new InetSocketAddress(ip, 0));
+        clientState.login(new AuthenticatedUser(userName));
+
+        assertThatThrownBy(clientState::validateLogin).isInstanceOf(UnauthorizedException.class).hasMessageContaining(
+            format("You do not have access from this IP %s", ip));
+    }
+
+    @Test
+    public void testCidrAccesses()
+    {
+        Map<String, List<String>> usersList = new HashMap<String, List<String>>()
+        {{
+            put("user1", Collections.singletonList("cidrGroup1"));
+            put("user2", Arrays.asList("cidrGroup2", "cidrGroup3"));
+        }};
+
+        Map<String, List<CIDR>> cidrsMapping = new HashMap<String, List<CIDR>>()
+        {{
+            put("cidrGroup1", Collections.singletonList(CIDR.getInstance("10.20.30.5/24")));
+            put("cidrGroup2", Arrays.asList(CIDR.getInstance("1111:2222:3333:4444:5555:6666:7777:8888/106"),
+                                            CIDR.getInstance("2001:3002::/16")));
+            put("cidrGroup3", Arrays.asList(CIDR.getInstance("40.50.60.7/32"), CIDR.getInstance("50.60.70.80/10"),
+                                            CIDR.getInstance("60.70.80.90/22")));
+        }};
+
+        AuthTestUtils.createUsersWithCidrAccess(usersList);
+        AuthTestUtils.insertCidrsMappings(cidrsMapping);
+
+        for (Map.Entry<String, List<String>> user : usersList.entrySet())
+        {
+            for (String cidrGroup : user.getValue())
+            {
+                List<CIDR> cidrsofCidrGroup = cidrsMapping.get(cidrGroup);
+                for (CIDR cidr : cidrsofCidrGroup)
+                {
+                    testValidCidrAccess(user.getKey(), cidr.getStartIpAddress().getHostAddress());
+                }
+            }
+        }
+
+        for (CIDR cidr : cidrsMapping.get("cidrGroup2"))
+        {
+            testInvalidCidrAccess("user1", cidr.getStartIpAddress().getHostAddress());
+        }
+
+        for (CIDR cidr : cidrsMapping.get("cidrGroup3"))
+        {
+            testInvalidCidrAccess("user1", cidr.getStartIpAddress().getHostAddress());
+        }
+
+        for (CIDR cidr : cidrsMapping.get("cidrGroup1"))
+        {
+            testInvalidCidrAccess("user2", cidr.getStartIpAddress().getHostAddress());
+        }
+
+        testInvalidCidrLogin("user1", "20.30.40.6");
+    }
+
+    @Test
+    public void testNonexistingCidrLogin()
+    {
+        AuthTestUtils.createUsersWithCidrAccess(Collections.singletonMap("user11",
+                                                                         Collections.singletonList("cidrGroup11")));
+        AuthTestUtils.insertCidrsMappings(Collections.singletonMap("cidrGroup11",
+                                                                   Collections.singletonList(
+                                                                   CIDR.getInstance("200.30.40.60/24"))));
+
+        testInvalidCidrLogin("user11", "250.30.40.60");
+    }
+
+    @Test
+    public void testSuperUserAccess()
+    {
+        testValidCidrAccess(CassandraRoleManager.DEFAULT_SUPERUSER_NAME, "255.255.255.255");
+    }
+
+    @Test
+    public void testUserWithoutCidrRestrictions()
+    {
+        String roleName = "role1";
+        AuthTestUtils.auth("CREATE ROLE %s WITH password = 'password' AND LOGIN = true ",
+             roleName);
+        testValidCidrAccess(roleName, "127.0.0.1");
+    }
+
+    @Test
+    public void testUserAllowedforAllCidrs()
+    {
+        String roleName = "role1";
+        AuthTestUtils.auth("CREATE ROLE %s WITH password = 'password' AND LOGIN = true AND ACCESS FROM ALL CIDRS",
+             roleName);
+        testValidCidrAccess(roleName, "17.0.0.1");
+    }
+
+    @Test
+    public void testUserWithoutLogin()
+    {
+        String roleName = "role1";
+        AuthTestUtils.auth("CREATE ROLE %s WITH password = 'password'", roleName);
+        testInvalidCidrAccess(roleName, "17.0.0.1");
+    }
+
+    private UntypedResultSet getCidrGroups(String role)
+    {
+        String query = String.format("SELECT %s FROM %s.%s WHERE role = '%s'",
+                                     "cidr_groups",
+                                     AUTH_KEYSPACE_NAME,
+                                     CIDR_PERMISSIONS,
+                                     RoleResource.role(role).getRoleName());
+        return QueryProcessor.executeInternal(query);
+    }
+
+    @Test
+    public void testCreateAndDropRoleWithCidrsClause() throws Throwable
+    {
+        String role = "role1";
+
+        assertEmpty(getCidrGroups(role));
+
+        AuthTestUtils.createUsersWithCidrAccess(Collections.singletonMap(role,
+                                                                         Collections.singletonList("cidrGroup1")));
+        UntypedResultSet results = getCidrGroups(role);
+        Assert.assertEquals(Sets.newHashSet("cidrGroup1"),
+                            Iterables.getOnlyElement(results).getFrozenSet("cidr_groups", UTF8Type.instance));
+
+        AuthTestUtils.auth("DROP ROLE %s", role);
+        assertEmpty(getCidrGroups(role));
+    }
+
+    @Test
+    public void testAlterAndDropRoleWithCidrsClause() throws Throwable
+    {
+        String role = "role1";
+
+        assertEmpty(getCidrGroups(role));
+
+        auth("CREATE ROLE %s WITH password = 'password' AND LOGIN = true ", role);
+        auth("ALTER ROLE %s WITH ACCESS FROM CIDRS {'%s'}", role, "cidrGroup1");
+
+        UntypedResultSet results = getCidrGroups(role);
+        Assert.assertEquals(Sets.newHashSet("cidrGroup1"),
+                            Iterables.getOnlyElement(results).getFrozenSet("cidr_groups", UTF8Type.instance));
+
+        AuthTestUtils.auth("DROP ROLE %s", role);
+        assertEmpty(getCidrGroups(role));
+    }
+
+    @Test
+    public void testInvalidCidrs()
+    {
+        Map<String, List<String>> usersList = new HashMap<String, List<String>>()
+        {{
+            put("user1", Collections.singletonList("cidrGroup1"));
+        }};
+
+        AuthTestUtils.createUsersWithCidrAccess(usersList);
+
+        QueryProcessor.executeInternal(format(
+            "insert into %s.%s(cidr_group, cidrs) values('cidrGroup1', { ('10.20.30.5', 33), ('11.20.30.6', 16) } );",
+            AUTH_KEYSPACE_NAME, CIDR_GROUPS));
+        DatabaseDescriptor.getCIDRAuthorizer().loadCidrGroupsCache(); // update cache with CIDRs inserted above
+
+        testInvalidCidrAccess("user1", "10.20.30.5");
+        testValidCidrAccess("user1", "11.20.30.60");
+    }
+
+    @Test
+    public void testCidrChecksForSuperUsers()
+    {
+        testValidCidrAccess(CassandraRoleManager.DEFAULT_SUPERUSER_NAME, "10.20.30.5");
+        testValidCidrAccess(CassandraRoleManager.DEFAULT_SUPERUSER_NAME, "200.30.40.60");
+
+        Config conf = DatabaseDescriptor.getRawConfig();
+        conf.cidr_authorizer = new ParameterizedClass(CassandraCIDRAuthorizer.class.getName(), new HashMap<>());
+        conf.cidr_authorizer.parameters.put("cidr_checks_for_superusers", String.valueOf(true));
+
+        AuthTestUtils.insertCidrsMappings(Collections.singletonMap("cidrGroup1",
+                                                                   Collections.singletonList(
+                                                                   CIDR.getInstance("200.30.40.60/24"))));
+        AuthTestUtils.auth("alter role %s with access from cidrs {'cidrGroup1'}",
+                           CassandraRoleManager.DEFAULT_SUPERUSER_NAME);
+        cidrAuthorizer.getCidrPermissionsCache().invalidate();
+
+        testInvalidCidrAccess(CassandraRoleManager.DEFAULT_SUPERUSER_NAME, "10.20.30.5");
+        testValidCidrAccess(CassandraRoleManager.DEFAULT_SUPERUSER_NAME, "200.30.40.60");
+    }
+}
diff --git a/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerMonitorModeTest.java b/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerMonitorModeTest.java
new file mode 100644
index 0000000000..fe92b2c886
--- /dev/null
+++ b/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerMonitorModeTest.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.auth;
+
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.HashMap;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.config.Config;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.config.ParameterizedClass;
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.service.ClientState;
+
+import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS;
+import static org.apache.cassandra.auth.AuthKeyspace.CIDR_PERMISSIONS;
+import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME;
+
+public class CassandraCIDRAuthorizerMonitorModeTest extends CQLTester
+{
+    private static final AuthTestUtils.LocalCassandraCIDRAuthorizer cidrAuthorizer =
+    new AuthTestUtils.LocalCassandraCIDRAuthorizer(ICIDRAuthorizer.CIDRAuthorizerMode.MONITOR);
+    private static void setupSuperUser()
+    {
+        QueryProcessor.executeInternal(String.format("INSERT INTO %s.%s (role, is_superuser, can_login, salted_hash) "
+                                                     + "VALUES ('%s', true, true, '%s')",
+                                                     AUTH_KEYSPACE_NAME,
+                                                     AuthKeyspace.ROLES,
+                                                     CassandraRoleManager.DEFAULT_SUPERUSER_NAME,
+                                                     "xxx"));
+    }
+
+    @BeforeClass
+    public static void defineSchema() throws ConfigurationException
+    {
+        SchemaLoader.prepareServer();
+
+        SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(),
+                               new AuthTestUtils.LocalPasswordAuthenticator(),
+                               new AuthTestUtils.LocalCassandraAuthorizer(),
+                               new AuthTestUtils.LocalCassandraNetworkAuthorizer(),
+                               cidrAuthorizer);
+        AuthCacheService.initializeAndRegisterCaches();
+        setupSuperUser();
+    }
+
+    @Before
+    public void before()
+    {
+        Keyspace.open(AUTH_KEYSPACE_NAME).getColumnFamilyStore(CIDR_GROUPS).truncateBlocking();
+        Keyspace.open(AUTH_KEYSPACE_NAME).getColumnFamilyStore(CIDR_PERMISSIONS).truncateBlocking();
+        cidrAuthorizer.getCidrPermissionsCache().invalidate();
+    }
+
+    private void testValidCidrAccess(String userName, String ip)
+    {
+        InetSocketAddress ipAddr = new InetSocketAddress(ip, 0);
+
+        AuthenticatedUser user = new AuthenticatedUser(userName);
+        Assert.assertTrue(user.hasAccessFromIp(ipAddr));
+
+        ClientState clientState = ClientState.forExternalCalls(ipAddr);
+        clientState.login(user);
+        clientState.validateLogin(); // expect no exception
+    }
+
+    @Test
+    public void testCidrAccesses()
+    {
+        AuthTestUtils.createUsersWithCidrAccess(Collections.singletonMap("user1",
+                                                                         Collections.singletonList("cidrGroup1")));
+        AuthTestUtils.insertCidrsMappings(Collections.singletonMap("cidrGroup1",
+                                                                   Collections.singletonList(
+                                                                   CIDR.getInstance("10.20.30.5/24"))));
+
+        testValidCidrAccess("user1", "10.20.30.5");
+        testValidCidrAccess("user1", "20.20.30.5");
+    }
+
+    @Test
+    public void testNonexistingCidrLogin()
+    {
+        AuthTestUtils.createUsersWithCidrAccess(Collections.singletonMap("user11",
+                                                                         Collections.singletonList("cidrGroup11")));
+        AuthTestUtils.insertCidrsMappings(Collections.singletonMap("cidrGroup11",
+                                                                   Collections.singletonList(
+                                                                   CIDR.getInstance("200.30.40.60/24"))));
+
+        testValidCidrAccess("user11", "250.30.40.60");
+    }
+
+    @Test
+    public void testCidrChecksForSuperUsers()
+    {
+        testValidCidrAccess(CassandraRoleManager.DEFAULT_SUPERUSER_NAME, "10.20.30.5");
+        testValidCidrAccess(CassandraRoleManager.DEFAULT_SUPERUSER_NAME, "200.30.40.60");
+
+        Config conf = DatabaseDescriptor.getRawConfig();
+        conf.cidr_authorizer = new ParameterizedClass(CassandraCIDRAuthorizer.class.getName(), new HashMap<>());
+        conf.cidr_authorizer.parameters.put("cidr_checks_for_superusers", String.valueOf(true));
+
+        AuthTestUtils.insertCidrsMappings(Collections.singletonMap("cidrGroup1",
+                                                                   Collections.singletonList(
+                                                                   CIDR.getInstance("200.30.40.60/24"))));
+        AuthTestUtils.auth("alter role %s with access from cidrs {'cidrGroup1'}",
+                           CassandraRoleManager.DEFAULT_SUPERUSER_NAME);
+        cidrAuthorizer.getCidrPermissionsCache().invalidate();
+
+        testValidCidrAccess(CassandraRoleManager.DEFAULT_SUPERUSER_NAME, "10.20.30.5");
+        testValidCidrAccess(CassandraRoleManager.DEFAULT_SUPERUSER_NAME, "200.30.40.60");
+    }
+}
diff --git a/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java b/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java
index 3a208f8f6b..ecfa790a2e 100644
--- a/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java
+++ b/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java
@@ -30,23 +30,21 @@ import org.junit.Test;
 
 import org.apache.cassandra.SchemaLoader;
 import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.CQLTester;
 import org.apache.cassandra.cql3.QueryProcessor;
 import org.apache.cassandra.cql3.UntypedResultSet;
-import org.apache.cassandra.cql3.statements.AlterRoleStatement;
-import org.apache.cassandra.cql3.statements.AuthenticationStatement;
-import org.apache.cassandra.cql3.statements.CreateRoleStatement;
-import org.apache.cassandra.cql3.statements.DropRoleStatement;
 import org.apache.cassandra.db.Keyspace;
 import org.apache.cassandra.db.marshal.UTF8Type;
 import org.apache.cassandra.exceptions.ConfigurationException;
-import org.apache.cassandra.service.ClientState;
 
 import static org.apache.cassandra.auth.AuthKeyspace.NETWORK_PERMISSIONS;
+import static org.apache.cassandra.auth.AuthTestUtils.auth;
 import static org.apache.cassandra.auth.AuthTestUtils.getRolesReadCount;
 import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
-public class CassandraNetworkAuthorizerTest
+public class CassandraNetworkAuthorizerTest extends CQLTester
 {
     private static void setupSuperUser()
     {
@@ -65,7 +63,8 @@ public class CassandraNetworkAuthorizerTest
         SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(),
                                new AuthTestUtils.LocalPasswordAuthenticator(),
                                new AuthTestUtils.LocalCassandraAuthorizer(),
-                               new AuthTestUtils.LocalCassandraNetworkAuthorizer());
+                               new AuthTestUtils.LocalCassandraNetworkAuthorizer(),
+                               new AuthTestUtils.LocalCassandraCIDRAuthorizer());
         AuthCacheService.initializeAndRegisterCaches();
         setupSuperUser();
     }
@@ -83,7 +82,7 @@ public class CassandraNetworkAuthorizerTest
                                      NETWORK_PERMISSIONS,
                                      RoleResource.role(username).getName());
         UntypedResultSet results = QueryProcessor.executeInternal(query);
-        Assert.assertTrue(results.isEmpty());
+        assertTrue(results != null && results.isEmpty());
     }
 
     private static void assertDcPermRow(String username, String... dcs)
@@ -94,6 +93,7 @@ public class CassandraNetworkAuthorizerTest
                                      NETWORK_PERMISSIONS,
                                      RoleResource.role(username).getName());
         UntypedResultSet results = QueryProcessor.executeInternal(query);
+        assertNotNull(results);
         UntypedResultSet.Row row = Iterables.getOnlyElement(results);
         Set<String> actual = row.getFrozenSet("dcs", UTF8Type.instance);
         Assert.assertEquals(expected, actual);
@@ -104,26 +104,6 @@ public class CassandraNetworkAuthorizerTest
         return RandomStringUtils.randomAlphabetic(8).toLowerCase();
     }
 
-    private static ClientState getClientState()
-    {
-        ClientState state = ClientState.forInternalCalls();
-        state.login(new AuthenticatedUser(CassandraRoleManager.DEFAULT_SUPERUSER_NAME));
-        return state;
-    }
-
-    private static void auth(String query, Object... args)
-    {
-        CQLStatement statement = QueryProcessor.parseStatement(String.format(query, args)).prepare(ClientState.forInternalCalls());
-        assert statement instanceof CreateRoleStatement
-               || statement instanceof AlterRoleStatement
-               || statement instanceof DropRoleStatement;
-        AuthenticationStatement authStmt = (AuthenticationStatement) statement;
-
-        // invalidate roles cache so that any changes to the underlying roles are picked up
-        Roles.cache.invalidate();
-        authStmt.execute(getClientState());
-    }
-
     private static DCPermissions dcPerms(String username)
     {
         AuthenticatedUser user = new AuthenticatedUser(username);
diff --git a/test/unit/org/apache/cassandra/auth/RolesTest.java b/test/unit/org/apache/cassandra/auth/RolesTest.java
index 5eced338ce..7cdbaf2856 100644
--- a/test/unit/org/apache/cassandra/auth/RolesTest.java
+++ b/test/unit/org/apache/cassandra/auth/RolesTest.java
@@ -49,7 +49,8 @@ public class RolesTest
         SchemaLoader.setupAuth(roleManager,
                                new AuthTestUtils.LocalPasswordAuthenticator(),
                                new AuthTestUtils.LocalCassandraAuthorizer(),
-                               new AuthTestUtils.LocalCassandraNetworkAuthorizer());
+                               new AuthTestUtils.LocalCassandraNetworkAuthorizer(),
+                               new AuthTestUtils.LocalCassandraCIDRAuthorizer());
 
         for (RoleResource role : ALL_ROLES)
             roleManager.createRole(AuthenticatedUser.ANONYMOUS_USER, role, new RoleOptions());
diff --git a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java
index 4b1890ffff..b1e9c1ec0d 100644
--- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java
+++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java
@@ -73,6 +73,8 @@ public class DatabaseDescriptorRefTest
     "org.apache.cassandra.auth.IAuthenticator",
     "org.apache.cassandra.auth.IAuthorizer",
     "org.apache.cassandra.auth.IInternodeAuthenticator",
+    "org.apache.cassandra.auth.ICIDRAuthorizer",
+    "org.apache.cassandra.auth.ICIDRAuthorizer$CIDRAuthorizerMode",
     "org.apache.cassandra.auth.INetworkAuthorizer",
     "org.apache.cassandra.auth.IRoleManager",
     "org.apache.cassandra.config.CassandraRelevantProperties",
diff --git a/test/unit/org/apache/cassandra/cql3/CIDRTest.java b/test/unit/org/apache/cassandra/cql3/CIDRTest.java
new file mode 100644
index 0000000000..7099b478f6
--- /dev/null
+++ b/test/unit/org/apache/cassandra/cql3/CIDRTest.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.cql3;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static org.junit.Assert.*;
+
+public class CIDRTest
+{
+    @Test
+    public void testIpv4StartingAndEndingIP()
+    {
+        CIDR cidr = CIDR.getInstance("255.255.255.25/24");
+        assertEquals("255.255.255.0/24", cidr.toString());
+        assertEquals("255.255.255.0", cidr.getStartIpAddress().getHostAddress());
+        assertEquals("255.255.255.255", cidr.getEndIpAddress().getHostAddress());
+
+        cidr = CIDR.getInstance("255.12.143.255/21");
+        assertEquals("255.12.136.0/21", cidr.toString());
+        assertEquals("255.12.136.0", cidr.getStartIpAddress().getHostAddress());
+        assertEquals("255.12.143.255", cidr.getEndIpAddress().getHostAddress());
+
+        cidr = CIDR.getInstance("255.25.55.255/31");
+        assertEquals("255.25.55.254/31", cidr.toString());
+        assertEquals("255.25.55.254", cidr.getStartIpAddress().getHostAddress());
+        assertEquals("255.25.55.255", cidr.getEndIpAddress().getHostAddress());
+
+        cidr = CIDR.getInstance("255.25.55.255/32");
+        assertEquals("255.25.55.255/32", cidr.toString());
+        assertEquals("255.25.55.255", cidr.getStartIpAddress().getHostAddress());
+        assertEquals("255.25.55.255", cidr.getEndIpAddress().getHostAddress());
+    }
+
+    @Test
+    public void testIpv6AdjustedToStartingIP()
+    {
+        CIDR cidr = CIDR.getInstance("2222:3333:4444:5555:6666:7777:8888:9999/16");
+        assertEquals("2222:0:0:0:0:0:0:0/16", cidr.toString());
+        assertEquals("2222:0:0:0:0:0:0:0", cidr.getStartIpAddress().getHostAddress());
+        assertEquals("2222:ffff:ffff:ffff:ffff:ffff:ffff:ffff", cidr.getEndIpAddress().getHostAddress());
+
+        cidr = CIDR.getInstance("2222:3333:4444:5555::/24");
+        assertEquals("2222:3300:0:0:0:0:0:0/24", cidr.toString());
+        assertEquals("2222:3300:0:0:0:0:0:0", cidr.getStartIpAddress().getHostAddress());
+        assertEquals("2222:33ff:ffff:ffff:ffff:ffff:ffff:ffff", cidr.getEndIpAddress().getHostAddress());
+
+        cidr = CIDR.getInstance("2222:3333::4444:550f/127");
+        assertEquals("2222:3333:0:0:0:0:4444:550e/127", cidr.toString());
+        assertEquals("2222:3333:0:0:0:0:4444:550e", cidr.getStartIpAddress().getHostAddress());
+        assertEquals("2222:3333:0:0:0:0:4444:550f", cidr.getEndIpAddress().getHostAddress());
+    }
+
+    @Test
+    public void testIpv4MappedIpv6()
+    {
+        CIDR cidr = CIDR.getInstance("::ffff:152.153.154.155/128");
+        assertEquals("152.153.154.155/32", cidr.toString());
+        assertEquals(32, cidr.getNetMask());
+        assertFalse(cidr.isIPv6());
+        assertTrue(cidr.isIPv4());
+        assertTrue(cidr.equals(CIDR.getInstance("152.153.154.155/32")));
+
+        cidr = CIDR.getInstance("::ffff:152.153.154.155/120");
+        assertEquals("152.153.154.0/24", cidr.toString());
+        assertEquals(24, cidr.getNetMask());
+        assertFalse(cidr.isIPv6());
+        assertTrue(cidr.isIPv4());
+        assertTrue(cidr.equals(CIDR.getInstance("152.153.154.0/24")));
+    }
+
+    @Test()
+    public void testCidrWithInvalidNetMask()
+    {
+        assertThatThrownBy(() -> CIDR.getInstance("100.100.100.100/33"))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("100.100.100.100/33 is not a valid CIDR String");
+
+        assertThatThrownBy(() -> new CIDR(InetAddress.getByName("100.100.100.100"), (short)33))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("Invalid netmask 33 for IP 100.100.100.100");
+    }
+
+    @Test()
+    public void testInvalidIpv4MappedIpv6Mask()
+    {
+        assertThatThrownBy((() -> CIDR.getInstance("::ffff:152.153.154.155/90").toString()))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("::ffff:152.153.154.155/90 is not a valid CIDR String");
+    }
+
+    @Test
+    public void testNullOrEmptyCidrString()
+    {
+        assertThatThrownBy((() -> CIDR.getInstance(null).toString()))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("null is not a valid CIDR String");
+
+        assertThatThrownBy((() -> CIDR.getInstance("").toString()))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining(" is not a valid CIDR String");
+    }
+
+    @Test
+    public void testCidrStringWithoutNetMask()
+    {
+        assertThatThrownBy((() -> CIDR.getInstance("10.20.30.40").toString()))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("10.20.30.40 is not a valid CIDR String");
+    }
+
+    @Test
+    public void testCidrStringWithInvalidIp()
+    {
+        assertThatThrownBy((() -> CIDR.getInstance("10.20.300.40/24").toString()))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("10.20.300.40/24 is not a valid CIDR String");
+    }
+
+    @Test
+    public void testCidrAsTupleString()
+    {
+        CIDR cidr = CIDR.getInstance("10.20.30.40/24");
+        assertEquals("('10.20.30.0', 24)", cidr.asCqlTupleString());
+    }
+
+    @Test
+    public void testCompareIPs() throws UnknownHostException
+    {
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("10.20.30.40"), InetAddress.getByName("10.20.30.41")) < 0);
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("10.20.30.40"), InetAddress.getByName("10.20.30.39")) > 0);
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("10.20.30.40"), InetAddress.getByName("10.20.30.40")) == 0);
+
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("2222:3333::4444:550f"),
+                                   InetAddress.getByName("2222:5555::4444:550f")) < 0);
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("2222:ffff:ffff:ffff:ffff:ffff:ffff:ffff"),
+                                   InetAddress.getByName("2222:3333::4444:550f")) > 0);
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("2222:3333::4444:550f"),
+                                   InetAddress.getByName("2222:3333:0000:0000::4444:550f")) == 0);
+
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("10.20.30.40"), InetAddress.getByName("10.22.40.40")) < 0);
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("10.24.40.40"), InetAddress.getByName("10.20.30.40")) > 0);
+
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("0.20.30.40"), InetAddress.getByName("255.20.40.40")) < 0);
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("255.20.40.40"), InetAddress.getByName("128.20.30.40")) > 0);
+
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("::ffff:152.153.154.155"),
+                                   InetAddress.getByName("152.154.154.155")) < 0);
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("::ffff:152.154.154.155"),
+                                   InetAddress.getByName("152.153.154.155")) > 0);
+        assertTrue(CIDR.compareIPs(InetAddress.getByName("::ffff:152.153.154.155"),
+                                   InetAddress.getByName("152.153.154.155")) == 0);
+    }
+
+    @Test
+    public void testCidrOverlaps()
+    {
+        assertFalse(CIDR.overlaps(CIDR.getInstance("10.20.30.0/24"), CIDR.getInstance("20.20.30.0/24")));
+        assertFalse(CIDR.overlaps(CIDR.getInstance("10.20.63.0/24"), CIDR.getInstance("10.20.79.0/24")));
+
+        assertTrue(CIDR.overlaps(CIDR.getInstance("10.20.30.0/24"), CIDR.getInstance("10.20.30.0/25")));
+        assertTrue(CIDR.overlaps(CIDR.getInstance("10.20.30.0/24"), CIDR.getInstance("10.20.30.0/24")));
+
+        assertTrue(CIDR.overlaps(CIDR.getInstance("::ffff:152.153.154.155/120"),
+                                 CIDR.getInstance("152.153.154.155/24")));
+        assertFalse(CIDR.overlaps(CIDR.getInstance("::ffff:152.153.154.155/120"),
+                                  CIDR.getInstance("::ffff:152.153.153.155/120")));
+
+        assertTrue(CIDR.overlaps(CIDR.getInstance("2222:3333:4444:5555:6666:7777:8888:9999/16"),
+                                 CIDR.getInstance("2222:3333:4444:5555:6666:7777:8888:9999/36")));
+        assertFalse(CIDR.overlaps(CIDR.getInstance("2222:3333:4444:5555:6666:7777:8888:9999/16"),
+                                  CIDR.getInstance("1010:3333:4444:5555:6666:7777:8888:9999/16")));
+    }
+}
diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java
index afdcfaf54e..85a3186648 100644
--- a/test/unit/org/apache/cassandra/cql3/CQLTester.java
+++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java
@@ -558,6 +558,7 @@ public abstract class CQLTester
         DatabaseDescriptor.setAuthenticator(new AuthTestUtils.LocalPasswordAuthenticator());
         DatabaseDescriptor.setAuthorizer(new AuthTestUtils.LocalCassandraAuthorizer());
         DatabaseDescriptor.setNetworkAuthorizer(new AuthTestUtils.LocalCassandraNetworkAuthorizer());
+        DatabaseDescriptor.setCIDRAuthorizer(new AuthTestUtils.LocalCassandraCIDRAuthorizer());
 
         // The CassandraRoleManager constructor set the supported and alterable options based on
         // DatabaseDescriptor authenticator type so it needs to be created only after the authenticator is set.
@@ -576,6 +577,7 @@ public abstract class CQLTester
         DatabaseDescriptor.getAuthenticator().setup();
         DatabaseDescriptor.getAuthorizer().setup();
         DatabaseDescriptor.getNetworkAuthorizer().setup();
+        DatabaseDescriptor.getCIDRAuthorizer().setup();
         Schema.instance.registerListener(new AuthSchemaChangeListener());
 
         AuthCacheService.initializeAndRegisterCaches();
diff --git a/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java
index e77ae35413..f90685782c 100644
--- a/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java
+++ b/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java
@@ -72,7 +72,8 @@ public class AddIdentityStatementTest
         SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(),
                                new AuthTestUtils.LocalPasswordAuthenticator(),
                                new AuthTestUtils.LocalCassandraAuthorizer(),
-                               new AuthTestUtils.LocalCassandraNetworkAuthorizer());
+                               new AuthTestUtils.LocalCassandraNetworkAuthorizer(),
+                               new AuthTestUtils.LocalCassandraCIDRAuthorizer());
         AuthCacheService.initializeAndRegisterCaches();
         setupPrivilegedUser();
     }
diff --git a/test/unit/org/apache/cassandra/cql3/statements/AlterRoleStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/AlterRoleStatementTest.java
index 883b4f5442..3526d672bb 100644
--- a/test/unit/org/apache/cassandra/cql3/statements/AlterRoleStatementTest.java
+++ b/test/unit/org/apache/cassandra/cql3/statements/AlterRoleStatementTest.java
@@ -22,6 +22,7 @@ import com.google.common.collect.Sets;
 import org.junit.Assert;
 import org.junit.Test;
 
+import org.apache.cassandra.auth.CIDRPermissions;
 import org.apache.cassandra.auth.DCPermissions;
 import org.apache.cassandra.cql3.CQLStatement;
 import org.apache.cassandra.cql3.QueryProcessor;
@@ -40,14 +41,19 @@ public class AlterRoleStatementTest
         return parse(query).dcPermissions;
     }
 
+    private static CIDRPermissions cidrPerms(String query)
+    {
+        return parse(query).cidrPermissions;
+    }
+
     @Test
-    public void dcsNotSpecified() throws Exception
+    public void dcsNotSpecified()
     {
         Assert.assertNull(dcPerms("ALTER ROLE r1 WITH PASSWORD = 'password'"));
     }
 
     @Test
-    public void dcsAllSpecified() throws Exception
+    public void dcsAllSpecified()
     {
         DCPermissions dcPerms = dcPerms("ALTER ROLE r1 WITH ACCESS TO ALL DATACENTERS");
         Assert.assertNotNull(dcPerms);
@@ -55,7 +61,7 @@ public class AlterRoleStatementTest
     }
 
     @Test
-    public void singleDc() throws Exception
+    public void singleDc()
     {
         DCPermissions dcPerms = dcPerms("ALTER ROLE r1 WITH ACCESS TO DATACENTERS {'dc1'}");
         Assert.assertNotNull(dcPerms);
@@ -64,11 +70,43 @@ public class AlterRoleStatementTest
     }
 
     @Test
-    public void multiDcs() throws Exception
+    public void multiDcs()
     {
         DCPermissions dcPerms = dcPerms("ALTER ROLE r1 WITH ACCESS TO DATACENTERS {'dc1', 'dc2'}");
         Assert.assertNotNull(dcPerms);
         Assert.assertTrue(dcPerms.restrictsAccess());
         Assert.assertEquals(Sets.newHashSet("dc1", "dc2"), dcPerms.allowedDCs());
     }
+
+    @Test
+    public void cidrsNotSpecified()
+    {
+        Assert.assertNull(cidrPerms("ALTER ROLE r1 WITH PASSWORD = 'password'"));
+    }
+
+    @Test
+    public void cidrsAllSpecified()
+    {
+        CIDRPermissions cidrPerms = cidrPerms("ALTER ROLE r1 WITH ACCESS FROM ALL CIDRS");
+        Assert.assertNotNull(cidrPerms);
+        Assert.assertFalse(cidrPerms.restrictsAccess());
+    }
+
+    @Test
+    public void singleCidr()
+    {
+        CIDRPermissions cidrPerms = cidrPerms("ALTER ROLE r1 WITH ACCESS FROM CIDRS {'region1'}");
+        Assert.assertNotNull(cidrPerms);
+        Assert.assertTrue(cidrPerms.restrictsAccess());
+        Assert.assertEquals(Sets.newHashSet("region1"), cidrPerms.allowedCIDRGroups());
+    }
+
+    @Test
+    public void multiCidrs()
+    {
+        CIDRPermissions cidrPerms = cidrPerms("ALTER ROLE r1 WITH ACCESS FROM CIDRS {'region1', 'region2'}");
+        Assert.assertNotNull(cidrPerms);
+        Assert.assertTrue(cidrPerms.restrictsAccess());
+        Assert.assertEquals(Sets.newHashSet("region1", "region2"), cidrPerms.allowedCIDRGroups());
+    }
 }
diff --git a/test/unit/org/apache/cassandra/cql3/statements/CreateRoleStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/CreateRoleStatementTest.java
index 7a23da2d19..3dd1afd2a0 100644
--- a/test/unit/org/apache/cassandra/cql3/statements/CreateRoleStatementTest.java
+++ b/test/unit/org/apache/cassandra/cql3/statements/CreateRoleStatementTest.java
@@ -22,6 +22,7 @@ import com.google.common.collect.Sets;
 import org.junit.Assert;
 import org.junit.Test;
 
+import org.apache.cassandra.auth.CIDRPermissions;
 import org.apache.cassandra.auth.DCPermissions;
 import org.apache.cassandra.cql3.CQLStatement;
 import org.apache.cassandra.cql3.CQLTester;
@@ -42,14 +43,19 @@ public class CreateRoleStatementTest extends CQLTester
         return parse(query).dcPermissions;
     }
 
+    private static CIDRPermissions cidrPerms(String query)
+    {
+        return parse(query).cidrPermissions;
+    }
+
     @Test
-    public void allDcsImplicit() throws Exception
+    public void allDcsImplicit()
     {
         Assert.assertFalse(dcPerms("CREATE ROLE role").restrictsAccess());
     }
 
     @Test
-    public void allDcsExplicit() throws Exception
+    public void allDcsExplicit()
     {
         Assert.assertFalse(dcPerms("CREATE ROLE role WITH ACCESS TO ALL DATACENTERS").restrictsAccess());
     }
@@ -63,11 +69,38 @@ public class CreateRoleStatementTest extends CQLTester
     }
 
     @Test
-    public void multiDcs() throws Exception
+    public void multiDcs()
     {
         DCPermissions perms = dcPerms("CREATE ROLE role WITH ACCESS TO DATACENTERS {'dc1', 'dc2'}");
         Assert.assertTrue(perms.restrictsAccess());
         Assert.assertEquals(Sets.newHashSet("dc1", "dc2"), perms.allowedDCs());
+    }
+
+    @Test
+    public void allCidrsImplicit() throws Exception
+    {
+        Assert.assertFalse(cidrPerms("CREATE ROLE role").restrictsAccess());
+    }
+
+    @Test
+    public void allCidrsExplicit()
+    {
+        Assert.assertFalse(dcPerms("CREATE ROLE role WITH ACCESS FROM ALL CIDRS").restrictsAccess());
+    }
+
+    @Test
+    public void singleCidr()
+    {
+        CIDRPermissions perms = cidrPerms("CREATE ROLE role WITH ACCESS FROM CIDRS {'aodc'}");
+        Assert.assertTrue(perms.restrictsAccess());
+        Assert.assertEquals(Sets.newHashSet("aodc"), perms.allowedCIDRGroups());
+    }
 
+    @Test
+    public void multiCidrs()
+    {
+        CIDRPermissions perms = cidrPerms("CREATE ROLE role WITH ACCESS FROM CIDRS {'aodc', 'aws'}");
+        Assert.assertTrue(perms.restrictsAccess());
+        Assert.assertEquals(Sets.newHashSet("aodc", "aws"), perms.allowedCIDRGroups());
     }
 }
diff --git a/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java
index 51c61bbb12..325e0be233 100644
--- a/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java
+++ b/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java
@@ -21,6 +21,7 @@ package org.apache.cassandra.cql3.statements;
 import org.junit.Assert;
 import org.junit.Test;
 
+import org.apache.cassandra.auth.CIDRPermissions;
 import org.apache.cassandra.auth.DCPermissions;
 import org.apache.cassandra.cql3.CQLStatement;
 import org.apache.cassandra.cql3.QueryProcessor;
@@ -39,9 +40,20 @@ public class CreateUserStatementTest
         return parse(query).dcPermissions;
     }
 
+    private static CIDRPermissions cidrPerms(String query)
+    {
+        return parse(query).cidrPermissions;
+    }
+
     @Test
-    public void allDcsImplicit() throws Exception
+    public void allDcsImplicit()
     {
         Assert.assertFalse(dcPerms("CREATE USER u1").restrictsAccess());
     }
+
+    @Test
+    public void allCidrsImplicit() throws Exception
+    {
+        Assert.assertFalse(cidrPerms("CREATE USER u2").restrictsAccess());
+    }
 }
diff --git a/test/unit/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTableTest.java
new file mode 100644
index 0000000000..889d862135
--- /dev/null
+++ b/test/unit/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTableTest.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.db.virtual;
+
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.codahale.metrics.Snapshot;
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.auth.AuthCacheService;
+import org.apache.cassandra.auth.AuthKeyspace;
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.auth.AuthenticatedUser;
+import org.apache.cassandra.auth.CassandraRoleManager;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.metrics.CIDRAuthorizerMetrics;
+
+import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS;
+import static org.apache.cassandra.auth.AuthKeyspace.CIDR_PERMISSIONS;
+import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class CIDRFilteringMetricsTableTest extends CQLTester
+{
+    private static final String KS_NAME = "vts";
+
+    private static void setupSuperUser()
+    {
+        QueryProcessor.executeInternal(String.format("INSERT INTO %s.%s (role, is_superuser, can_login, salted_hash) " +
+                                                     "VALUES ('%s', true, true, '%s')",
+                                                     AUTH_KEYSPACE_NAME,
+                                                     AuthKeyspace.ROLES,
+                                                     CassandraRoleManager.DEFAULT_SUPERUSER_NAME,
+                                                     "xxx"));
+    }
+
+    @BeforeClass
+    public static void defineSchema() throws ConfigurationException
+    {
+        SchemaLoader.prepareServer();
+
+        SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(),
+                               new AuthTestUtils.LocalPasswordAuthenticator(),
+                               new AuthTestUtils.LocalCassandraAuthorizer(),
+                               new AuthTestUtils.LocalCassandraNetworkAuthorizer(),
+                               new AuthTestUtils.LocalCassandraCIDRAuthorizer());
+        AuthCacheService.initializeAndRegisterCaches();
+        setupSuperUser();
+    }
+
+    @Before
+    public void clear()
+    {
+        Keyspace.open(AUTH_KEYSPACE_NAME).getColumnFamilyStore(CIDR_GROUPS).truncateBlocking();
+        Keyspace.open(AUTH_KEYSPACE_NAME).getColumnFamilyStore(CIDR_PERMISSIONS).truncateBlocking();
+    }
+
+    private void queryAndValidateCountMetrics() throws Throwable
+    {
+        CIDRAuthorizerMetrics cidrAuthorizerMetrics = DatabaseDescriptor.getCIDRAuthorizer().getCidrAuthorizerMetrics();
+
+        String getMetricsQuery = "SELECT * FROM " + KS_NAME + '.' +
+                                 CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable.TABLE_NAME;
+        UntypedResultSet vtsRows = execute(getMetricsQuery);
+
+        assertEquals(5, vtsRows.size());
+
+        Iterator<UntypedResultSet.Row> it = vtsRows.iterator();
+        while (it.hasNext())
+        {
+            UntypedResultSet.Row row = it.next();
+            String metricName = row.getString(CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable.NAME_COL);
+            long metricValue = row.getLong(CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable.VALUE_COL);
+            assertTrue(metricValue != 0);
+
+            if (CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable.CIDR_GROUPS_CACHE_RELOAD_COUNT_NAME
+                .equals(metricName))
+                assertEquals(cidrAuthorizerMetrics.cacheReloadCount.getCount(), metricValue, 0);
+            else
+            {
+                if (metricName.contains(CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable
+                                        .CIDR_ACCESSES_REJECTED_COUNT_NAME_PREFIX))
+                    assertEquals(cidrAuthorizerMetrics.rejectedCidrAccessCount.get(metricName.split(
+                        CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable
+                        .CIDR_ACCESSES_REJECTED_COUNT_NAME_PREFIX)[1]).getCount(), metricValue, 0);
+                else
+                    assertEquals(cidrAuthorizerMetrics.acceptedCidrAccessCount.get(metricName.split(
+                        CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable
+                        .CIDR_ACCESSES_ACCEPTED_COUNT_NAME_PREFIX)[1]).getCount(), metricValue, 0);
+            }
+        }
+    }
+
+    private void verifyLatencies(Snapshot snapshot, UntypedResultSet.Row row)
+    {
+        assertEquals(snapshot.getMedian(),
+                     row.getDouble(CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable.P50_COL), 0.01);
+        assertEquals(snapshot.get95thPercentile(),
+                     row.getDouble(CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable.P95_COL), 0.01);
+        assertEquals(snapshot.get99thPercentile(),
+                     row.getDouble(CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable.P99_COL), 0.01);
+        assertEquals(snapshot.get999thPercentile(),
+                     row.getDouble(CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable.P999_COL), 0.01);
+        assertEquals(snapshot.getMax(),
+                     row.getDouble(CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable.MAX_COL), 0.01);
+    }
+
+    private void queryAndValidateLatencyMetrics() throws Throwable
+    {
+        CIDRAuthorizerMetrics cidrAuthorizerMetrics = DatabaseDescriptor.getCIDRAuthorizer().getCidrAuthorizerMetrics();
+
+        String getMetricsQuery = "SELECT * FROM " + KS_NAME + '.' +
+                                 CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable.TABLE_NAME;
+        UntypedResultSet vtsRows = execute(getMetricsQuery);
+
+        assertEquals(3, vtsRows.size());
+
+        Iterator<UntypedResultSet.Row> it = vtsRows.iterator();
+        while (it.hasNext())
+        {
+            UntypedResultSet.Row row = it.next();
+            String metricName = row.getString(CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable.NAME_COL);
+
+            switch (metricName)
+            {
+                case CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable.CIDR_CHECKS_LATENCY_NAME:
+                    verifyLatencies(cidrAuthorizerMetrics.cidrChecksLatency.getSnapshot(), row);
+                    break;
+                case CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable.CIDR_GROUPS_CACHE_RELOAD_LATENCY_NAME:
+                    verifyLatencies(cidrAuthorizerMetrics.cacheReloadLatency.getSnapshot(), row);
+                    break;
+                case CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable.LOOKUP_CIDR_GROUPS_FOR_IP_LATENCY_NAME:
+                    verifyLatencies(cidrAuthorizerMetrics.lookupCidrGroupsForIpLatency.getSnapshot(), row);
+                    break;
+            }
+        }
+    }
+
+    @Test
+    public void testCidrFilteringStats() throws Throwable
+    {
+        CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable countsTable =
+        new CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable(KS_NAME);
+
+        CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable latenciesTable =
+        new CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable(KS_NAME);
+
+        VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(countsTable,
+                                                                                                latenciesTable)));
+
+        Map<String, List<String>> usersList = new HashMap<String, List<String>>()
+        {{
+            put("user1", Collections.singletonList("cidrGroup1"));
+            put("user2", Arrays.asList("cidrGroup2", "cidrGroup3"));
+        }};
+
+        Map<String, List<CIDR>> cidrsMapping = new HashMap<String, List<CIDR>>()
+        {{
+            put("cidrGroup1", Collections.singletonList(CIDR.getInstance("10.20.30.5/24")));
+            put("cidrGroup2", Arrays.asList(CIDR.getInstance("20.30.40.6/16"), CIDR.getInstance("30.40.50.6/8")));
+            put("cidrGroup3", Arrays.asList(CIDR.getInstance("40.50.60.7/32"), CIDR.getInstance("50.60.70.80/10"),
+                                            CIDR.getInstance("60.70.80.90/22")));
+        }};
+
+        AuthTestUtils.createUsersWithCidrAccess(usersList);
+        AuthTestUtils.insertCidrsMappings(cidrsMapping);
+
+        AuthenticatedUser user = new AuthenticatedUser("user1");
+        assertTrue(user.hasAccessFromIp(new InetSocketAddress("10.20.30.5", 0)));
+        Assert.assertFalse(user.hasAccessFromIp(new InetSocketAddress("11.20.30.5", 0)));
+        Assert.assertFalse(user.hasAccessFromIp(new InetSocketAddress("20.30.140.60", 0)));
+        Assert.assertFalse(user.hasAccessFromIp(new InetSocketAddress("50.60.170.180", 0)));
+
+        queryAndValidateCountMetrics();
+        queryAndValidateLatencyMetrics();
+    }
+
+}
diff --git a/test/unit/org/apache/cassandra/tools/nodetool/CIDRFilteringStatsTest.java b/test/unit/org/apache/cassandra/tools/nodetool/CIDRFilteringStatsTest.java
new file mode 100644
index 0000000000..ec415df09b
--- /dev/null
+++ b/test/unit/org/apache/cassandra/tools/nodetool/CIDRFilteringStatsTest.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.tools.nodetool;
+
+import java.net.InetSocketAddress;
+import java.util.Collections;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.auth.AuthCacheService;
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.auth.AuthenticatedUser;
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable;
+import org.apache.cassandra.db.virtual.VirtualKeyspace;
+import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class CIDRFilteringStatsTest extends CQLTester
+{
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        CQLTester.setUpClass();
+        CQLTester.requireAuthentication();
+        AuthCacheService.initializeAndRegisterCaches();
+
+        String KS_NAME = SchemaConstants.VIRTUAL_VIEWS;
+        VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME,
+                                                                      CIDRFilteringMetricsTable.getAll(KS_NAME)));
+
+        startJMXServer();
+    }
+
+    @Before
+    public void before()
+    {
+        AuthTestUtils.createUsersWithCidrAccess(Collections.singletonMap("user1",
+                                                                         Collections.singletonList("cidrGroup1")));
+
+        AuthTestUtils.insertCidrsMappings(Collections.singletonMap("cidrGroup1",
+                                                                   Collections.singletonList(
+                                                                   CIDR.getInstance("10.20.30.5/24"))));
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testMaybeChangeDocs()
+    {
+        // If you added, modified options or help, please update docs if necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", "cidrfilteringstats");
+        tool.assertOnCleanExit();
+
+        String help = "NAME\n" +
+                      "        nodetool cidrfilteringstats - Print statistics on CIDR filtering\n" +
+                      "\n" +
+                      "SYNOPSIS\n" +
+                      "        nodetool [(-h <host> | --host <host>)] [(-p <port> | --port <port>)]\n" +
+                      "                [(-pp | --print-port)] [(-pw <password> | --password <password>)]\n" +
+                      "                [(-pwf <passwordFilePath> | --password-file <passwordFilePath>)]\n" +
+                      "                [(-u <username> | --username <username>)] cidrfilteringstats\n" +
+                      "\n" +
+                      "OPTIONS\n" +
+                      "        -h <host>, --host <host>\n" +
+                      "            Node hostname or ip address\n" +
+                      "\n" +
+                      "        -p <port>, --port <port>\n" +
+                      "            Remote jmx agent port number\n" +
+                      "\n" +
+                      "        -pp, --print-port\n" +
+                      "            Operate in 4.0 mode with hosts disambiguated by port number\n" +
+                      "\n" +
+                      "        -pw <password>, --password <password>\n" +
+                      "            Remote jmx agent password\n" +
+                      "\n" +
+                      "        -pwf <passwordFilePath>, --password-file <passwordFilePath>\n" +
+                      "            Path to the JMX password file\n" +
+                      "\n" +
+                      "        -u <username>, --username <username>\n" +
+                      "            Remote jmx agent username\n" +
+                      "\n" +
+                      "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    @Test
+    public void testCIDRFilteringStats()
+    {
+        AuthenticatedUser user = new AuthenticatedUser("user1");
+        assertTrue(user.hasAccessFromIp(new InetSocketAddress("10.20.30.5", 0)));
+        assertFalse(user.hasAccessFromIp(new InetSocketAddress("11.20.30.5", 0)));
+
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("cidrfilteringstats");
+        tool.assertOnCleanExit();
+
+        String output = tool.getStdout();
+
+        assertThat(output).contains(CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable
+                                    .CIDR_ACCESSES_ACCEPTED_COUNT_NAME_PREFIX);
+        assertThat(output).contains(CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable
+                                    .CIDR_ACCESSES_REJECTED_COUNT_NAME_PREFIX);
+        assertThat(output).contains(CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable
+                                    .CIDR_GROUPS_CACHE_RELOAD_COUNT_NAME);
+
+        assertThat(output).contains(CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable
+                                    .CIDR_CHECKS_LATENCY_NAME);
+        assertThat(output).contains(CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable
+                                    .CIDR_GROUPS_CACHE_RELOAD_LATENCY_NAME);
+        assertThat(output).contains(CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable
+                                    .LOOKUP_CIDR_GROUPS_FOR_IP_LATENCY_NAME);
+    }
+}
diff --git a/test/unit/org/apache/cassandra/tools/nodetool/DropCIDRGroupTest.java b/test/unit/org/apache/cassandra/tools/nodetool/DropCIDRGroupTest.java
new file mode 100644
index 0000000000..4ec303068f
--- /dev/null
+++ b/test/unit/org/apache/cassandra/tools/nodetool/DropCIDRGroupTest.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.tools.nodetool;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.auth.AuthCacheService;
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DropCIDRGroupTest extends CQLTester
+{
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        CQLTester.setUpClass();
+        CQLTester.requireAuthentication();
+        AuthCacheService.initializeAndRegisterCaches();
+
+        startJMXServer();
+    }
+
+    @Before
+    public void insertCidrGroups()
+    {
+        Map<String, List<CIDR>> cidrsMapping = new HashMap<String, List<CIDR>>()
+        {{
+            put("test1", Arrays.asList(CIDR.getInstance("10.11.12.0/24")));
+            put("test2", Arrays.asList(CIDR.getInstance("11.11.12.0/24"), CIDR.getInstance("12.11.12.0/18")));
+        }};
+
+        AuthTestUtils.insertCidrsMappings(cidrsMapping);
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testDropCidrGroupDoc()
+    {
+        // If you added, modified options or help, please update docs if necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", "dropcidrgroup");
+        tool.assertOnCleanExit();
+
+        String help = "NAME\n" +
+                      "        nodetool dropcidrgroup - Drop an existing cidr group\n" +
+                      "\n" +
+                      "SYNOPSIS\n" +
+                      "        nodetool [(-h <host> | --host <host>)] [(-p <port> | --port <port>)]\n" +
+                      "                [(-pp | --print-port)] [(-pw <password> | --password <password>)]\n" +
+                      "                [(-pwf <passwordFilePath> | --password-file <passwordFilePath>)]\n" +
+                      "                [(-u <username> | --username <username>)] dropcidrgroup [--] <cidrGroup>\n" +
+                      "\n" +
+                      "OPTIONS\n" +
+                      "        -h <host>, --host <host>\n" +
+                      "            Node hostname or ip address\n" +
+                      "\n" +
+                      "        -p <port>, --port <port>\n" +
+                      "            Remote jmx agent port number\n" +
+                      "\n" +
+                      "        -pp, --print-port\n" +
+                      "            Operate in 4.0 mode with hosts disambiguated by port number\n" +
+                      "\n" +
+                      "        -pw <password>, --password <password>\n" +
+                      "            Remote jmx agent password\n" +
+                      "\n" +
+                      "        -pwf <passwordFilePath>, --password-file <passwordFilePath>\n" +
+                      "            Path to the JMX password file\n" +
+                      "\n" +
+                      "        -u <username>, --username <username>\n" +
+                      "            Remote jmx agent username\n" +
+                      "\n" +
+                      "        --\n" +
+                      "            This option can be used to separate command-line options from the\n" +
+                      "            list of argument, (useful when arguments might be mistaken for\n" +
+                      "            command-line options\n" +
+                      "\n" +
+                      "        <cidrGroup>\n" +
+                      "            Requires a cidr group name\n" +
+                      "\n" +
+                      "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    @Test
+    public void testDropCidrGroup()
+    {
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("listcidrgroups");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("test1");
+        assertThat(tool.getStdout()).contains("test2");
+
+        tool = ToolRunner.invokeNodetool("dropcidrgroup", "test1");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("Deleted CIDR group test1");
+
+        tool = ToolRunner.invokeNodetool("listcidrgroups");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("test2");
+
+        tool = ToolRunner.invokeNodetool("dropcidrgroup", "test2");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("Deleted CIDR group test2");
+
+        tool = ToolRunner.invokeNodetool("listcidrgroups");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+    }
+
+    @Test
+    public void testDropNonExistingCidrGroup()
+    {
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("dropcidrgroup", "unknown");
+        assertThat(tool.getStderr()).contains("CIDR group 'unknown' doesn't exists");
+        assertThat(tool.getStdout()).doesNotContain("Deleted CIDR group");
+    }
+}
diff --git a/test/unit/org/apache/cassandra/tools/nodetool/GetCIDRGroupsOfIPTest.java b/test/unit/org/apache/cassandra/tools/nodetool/GetCIDRGroupsOfIPTest.java
new file mode 100644
index 0000000000..e8844053c3
--- /dev/null
+++ b/test/unit/org/apache/cassandra/tools/nodetool/GetCIDRGroupsOfIPTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.tools.nodetool;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.auth.AuthCacheService;
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GetCIDRGroupsOfIPTest extends CQLTester
+{
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        CQLTester.setUpClass();
+        CQLTester.requireAuthentication();
+        AuthCacheService.initializeAndRegisterCaches();
+
+        startJMXServer();
+    }
+
+    @Before
+    public void before()
+    {
+        Map<String, List<CIDR>> cidrsMapping = new HashMap<String, List<CIDR>>()
+        {{
+            put("test1", Arrays.asList(CIDR.getInstance("10.11.12.0/24")));
+            put("test2", Arrays.asList(CIDR.getInstance("11.11.12.0/24"), CIDR.getInstance("12.11.12.0/18")));
+        }};
+
+        AuthTestUtils.insertCidrsMappings(cidrsMapping);
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testGetCidrGroupsOfIpDoc()
+    {
+        // If you added, modified options or help, please update docs if necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", "getcidrgroupsofip");
+        tool.assertOnCleanExit();
+
+        String help = "NAME\n" +
+                      "        nodetool getcidrgroupsofip - Print CIDR groups associated with given IP\n" +
+                      "\n" +
+                      "SYNOPSIS\n" +
+                      "        nodetool [(-h <host> | --host <host>)] [(-p <port> | --port <port>)]\n" +
+                      "                [(-pp | --print-port)] [(-pw <password> | --password <password>)]\n" +
+                      "                [(-pwf <passwordFilePath> | --password-file <passwordFilePath>)]\n" +
+                      "                [(-u <username> | --username <username>)] getcidrgroupsofip [--] <IP\n" +
+                      "                address>\n" +
+                      "\n" +
+                      "OPTIONS\n" +
+                      "        -h <host>, --host <host>\n" +
+                      "            Node hostname or ip address\n" +
+                      "\n" +
+                      "        -p <port>, --port <port>\n" +
+                      "            Remote jmx agent port number\n" +
+                      "\n" +
+                      "        -pp, --print-port\n" +
+                      "            Operate in 4.0 mode with hosts disambiguated by port number\n" +
+                      "\n" +
+                      "        -pw <password>, --password <password>\n" +
+                      "            Remote jmx agent password\n" +
+                      "\n" +
+                      "        -pwf <passwordFilePath>, --password-file <passwordFilePath>\n" +
+                      "            Path to the JMX password file\n" +
+                      "\n" +
+                      "        -u <username>, --username <username>\n" +
+                      "            Remote jmx agent username\n" +
+                      "\n" +
+                      "        --\n" +
+                      "            This option can be used to separate command-line options from the\n" +
+                      "            list of argument, (useful when arguments might be mistaken for\n" +
+                      "            command-line options\n" +
+                      "\n" +
+                      "        <IP address>\n" +
+                      "            Requires IP address as a string\n" +
+                      "\n" +
+                      "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    @Test
+    public void testGetCidrGroupsOfIp()
+    {
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("getcidrgroupsofip", "10.11.12.1");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("test1");
+
+        tool = ToolRunner.invokeNodetool("getcidrgroupsofip", "11.11.12.12");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("test2");
+
+        tool = ToolRunner.invokeNodetool("getcidrgroupsofip", "12.11.12.12");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("test2");;
+    }
+}
diff --git a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCIDRPermissionsCacheTest.java b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCIDRPermissionsCacheTest.java
new file mode 100644
index 0000000000..90be844b63
--- /dev/null
+++ b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCIDRPermissionsCacheTest.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.tools.nodetool;
+
+import java.net.InetSocketAddress;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.auth.AuthCacheService;
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.auth.AuthenticatedUser;
+import org.apache.cassandra.auth.IRoleManager;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A;
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B;
+import static org.apache.cassandra.auth.AuthTestUtils.getCidrPermissionsReadCount;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InvalidateCIDRPermissionsCacheTest extends CQLTester
+{
+    static InetSocketAddress ipAddr;
+
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        CQLTester.setUpClass();
+        CQLTester.requireAuthentication();
+
+        IRoleManager roleManager = DatabaseDescriptor.getRoleManager();
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions());
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions());
+        AuthCacheService.initializeAndRegisterCaches();
+
+        startJMXServer();
+
+        ipAddr = new InetSocketAddress("127.0.0.0", 0);
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testMaybeChangeDocs()
+    {
+        // If you added, modified options or help, please update docs if necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", "invalidatecidrpermissionscache");
+        tool.assertOnCleanExit();
+
+        String help = "NAME\n" +
+                      "        nodetool invalidatecidrpermissionscache - Invalidate the cidr\n" +
+                      "        permissions cache\n" +
+                      "\n" +
+                      "SYNOPSIS\n" +
+                      "        nodetool [(-h <host> | --host <host>)] [(-p <port> | --port <port>)]\n" +
+                      "                [(-pp | --print-port)] [(-pw <password> | --password <password>)]\n" +
+                      "                [(-pwf <passwordFilePath> | --password-file <passwordFilePath>)]\n" +
+                      "                [(-u <username> | --username <username>)] invalidatecidrpermissionscache\n" +
+                      "                [--] [<role>...]\n" +
+                      "\n" +
+                      "OPTIONS\n" +
+                      "        -h <host>, --host <host>\n" +
+                      "            Node hostname or ip address\n" +
+                      "\n" +
+                      "        -p <port>, --port <port>\n" +
+                      "            Remote jmx agent port number\n" +
+                      "\n" +
+                      "        -pp, --print-port\n" +
+                      "            Operate in 4.0 mode with hosts disambiguated by port number\n" +
+                      "\n" +
+                      "        -pw <password>, --password <password>\n" +
+                      "            Remote jmx agent password\n" +
+                      "\n" +
+                      "        -pwf <passwordFilePath>, --password-file <passwordFilePath>\n" +
+                      "            Path to the JMX password file\n" +
+                      "\n" +
+                      "        -u <username>, --username <username>\n" +
+                      "            Remote jmx agent username\n" +
+                      "\n" +
+                      "        --\n" +
+                      "            This option can be used to separate command-line options from the\n" +
+                      "            list of argument, (useful when arguments might be mistaken for\n" +
+                      "            command-line options\n" +
+                      "\n" +
+                      "        [<role>...]\n" +
+                      "            List of roles to invalidate. By default, all roles\n" +
+                      "\n" +
+                      "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    @Test
+    public void testInvalidateSingleCidrPermission()
+    {
+        AuthenticatedUser role = new AuthenticatedUser(ROLE_A.getRoleName());
+
+        // cache cidr permission
+        role.hasAccessFromIp(ipAddr);
+        long originalReadsCount = getCidrPermissionsReadCount();
+
+        // ensure cidr permission is cached
+        role.hasAccessFromIp(ipAddr);
+        assertThat(originalReadsCount).isEqualTo(getCidrPermissionsReadCount());
+
+        // invalidate cidr permission
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("invalidatecidrpermissionscache", ROLE_A.getRoleName());
+        tool.assertOnCleanExit();
+
+        // ensure cidr permission is reloaded
+        assertThat(role.hasAccessFromIp(new InetSocketAddress("127.0.0.0", 0))).isTrue();
+    }
+
+    @Test
+    public void testInvalidateAllCidrPermissions()
+    {
+        AuthenticatedUser roleA = new AuthenticatedUser(ROLE_A.getRoleName());
+        AuthenticatedUser roleB = new AuthenticatedUser(ROLE_B.getRoleName());
+
+        // cache cidr permissions
+        roleA.hasAccessFromIp(ipAddr);
+        roleB.hasAccessFromIp(ipAddr);
+        long originalReadsCount = getCidrPermissionsReadCount();
+
+        // enure cidr permissions are cached
+        assertThat(roleA.hasAccessFromIp(ipAddr)).isTrue();
+        assertThat(roleB.hasAccessFromIp(ipAddr)).isTrue();
+        assertThat(originalReadsCount).isEqualTo(getCidrPermissionsReadCount());
+
+        // invalidate both cidr permissions
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("invalidatecidrpermissionscache");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("Invalidated CIDR permissions cache");
+
+        // ensure cidr permission for roleA is reloaded
+        assertThat(roleA.hasAccessFromIp(ipAddr)).isTrue();
+        long readsCountAfterFirstReLoad = getCidrPermissionsReadCount();
+        assertThat(originalReadsCount).isLessThan(readsCountAfterFirstReLoad);
+
+        // ensure cidr permission for roleB is reloaded
+        assertThat(roleB.hasAccessFromIp(ipAddr)).isTrue();
+        long readsCountAfterSecondReLoad = getCidrPermissionsReadCount();
+        assertThat(readsCountAfterFirstReLoad).isLessThan(readsCountAfterSecondReLoad);
+    }
+
+    @Test
+    public void testInvalidateNonExistingRole()
+    {
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("invalidatecidrpermissionscache", "role1");
+        assertThat(tool.getStdout()).contains("Not found role role1 in CIDR permissions cache, nothing to invalidate");
+    }
+}
diff --git a/test/unit/org/apache/cassandra/tools/nodetool/ListCIDRGroupTest.java b/test/unit/org/apache/cassandra/tools/nodetool/ListCIDRGroupTest.java
new file mode 100644
index 0000000000..a6b377d9dc
--- /dev/null
+++ b/test/unit/org/apache/cassandra/tools/nodetool/ListCIDRGroupTest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.tools.nodetool;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.auth.AuthCacheService;
+import org.apache.cassandra.cql3.CIDR;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static org.apache.cassandra.auth.AuthTestUtils.insertCidrsMappings;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ListCIDRGroupTest extends CQLTester
+{
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        CQLTester.setUpClass();
+        CQLTester.requireAuthentication();
+        AuthCacheService.initializeAndRegisterCaches();
+
+        startJMXServer();
+    }
+
+    @Before
+    public void before()
+    {
+        Map<String, List<CIDR>> cidrsMapping = new HashMap<String, List<CIDR>>()
+        {{
+            put("test1", Collections.singletonList(CIDR.getInstance("10.11.12.0/24")));
+            put("test2", Arrays.asList(CIDR.getInstance("11.11.12.0/24"), CIDR.getInstance("12.11.12.0/18")));
+        }};
+
+        insertCidrsMappings(cidrsMapping);
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testListCidrGroupDoc()
+    {
+        // If you added, modified options or help, please update docs if necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", "listcidrgroups");
+        tool.assertOnCleanExit();
+
+        String help = "NAME\n" +
+                      "        nodetool listcidrgroups - List existing cidr groups\n" +
+                      "\n" +
+                      "SYNOPSIS\n" +
+                      "        nodetool [(-h <host> | --host <host>)] [(-p <port> | --port <port>)]\n" +
+                      "                [(-pp | --print-port)] [(-pw <password> | --password <password>)]\n" +
+                      "                [(-pwf <passwordFilePath> | --password-file <passwordFilePath>)]\n" +
+                      "                [(-u <username> | --username <username>)] listcidrgroups [--]\n" +
+                      "                [<cidrGroup>]\n" +
+                      "\n" +
+                      "OPTIONS\n" +
+                      "        -h <host>, --host <host>\n" +
+                      "            Node hostname or ip address\n" +
+                      "\n" +
+                      "        -p <port>, --port <port>\n" +
+                      "            Remote jmx agent port number\n" +
+                      "\n" +
+                      "        -pp, --print-port\n" +
+                      "            Operate in 4.0 mode with hosts disambiguated by port number\n" +
+                      "\n" +
+                      "        -pw <password>, --password <password>\n" +
+                      "            Remote jmx agent password\n" +
+                      "\n" +
+                      "        -pwf <passwordFilePath>, --password-file <passwordFilePath>\n" +
+                      "            Path to the JMX password file\n" +
+                      "\n" +
+                      "        -u <username>, --username <username>\n" +
+                      "            Remote jmx agent username\n" +
+                      "\n" +
+                      "        --\n" +
+                      "            This option can be used to separate command-line options from the\n" +
+                      "            list of argument, (useful when arguments might be mistaken for\n" +
+                      "            command-line options\n" +
+                      "\n" +
+                      "        [<cidrGroup>]\n" +
+                      "            LIST operation can be invoked with or without cidr group name\n" +
+                      "\n" +
+                      "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    @Test
+    public void testListCidrGroup()
+    {
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("listcidrgroups");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("test1");
+        assertThat(tool.getStdout()).contains("test2");
+
+        tool = ToolRunner.invokeNodetool("listcidrgroups", "test1");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("10.11.12.0/24");
+
+        tool = ToolRunner.invokeNodetool("listcidrgroups", "test2");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("11.11.12.0/24");
+        assertThat(tool.getStdout()).contains("12.11.0.0/18");
+    }
+}
diff --git a/test/unit/org/apache/cassandra/tools/nodetool/ReloadCIDRGroupsCacheTest.java b/test/unit/org/apache/cassandra/tools/nodetool/ReloadCIDRGroupsCacheTest.java
new file mode 100644
index 0000000000..3237d77c32
--- /dev/null
+++ b/test/unit/org/apache/cassandra/tools/nodetool/ReloadCIDRGroupsCacheTest.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.tools.nodetool;
+
+import java.net.InetSocketAddress;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.auth.AuthCacheService;
+import org.apache.cassandra.auth.AuthTestUtils;
+import org.apache.cassandra.auth.AuthenticatedUser;
+import org.apache.cassandra.auth.IRoleManager;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static java.lang.String.format;
+import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS;
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A;
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B;
+import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ReloadCIDRGroupsCacheTest extends CQLTester
+{
+    static InetSocketAddress ipAddr;
+
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        CQLTester.setUpClass();
+        CQLTester.requireAuthentication();
+
+        IRoleManager roleManager = DatabaseDescriptor.getRoleManager();
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions());
+        roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions());
+        AuthCacheService.initializeAndRegisterCaches();
+
+        startJMXServer();
+
+        ipAddr = new InetSocketAddress("127.0.0.0", 0);
+    }
+
+    @Before
+    public void before() throws Throwable
+    {
+        execute(format("insert into %s.%s (cidr_group, cidrs) values('test1', { ('10.11.12.0', 24) })",
+                       AUTH_KEYSPACE_NAME, CIDR_GROUPS));
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testMaybeChangeDocs()
+    {
+        // If you added, modified options or help, please update docs if necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", "reloadcidrgroupscache");
+        tool.assertOnCleanExit();
+
+        String help = "NAME\n" +
+                      "        nodetool reloadcidrgroupscache - Reload CIDR groups cache with latest\n" +
+                      "        entries in cidr_groups table, when CIDR authorizer is enabled\n" +
+                      "\n" +
+                      "SYNOPSIS\n" +
+                      "        nodetool [(-h <host> | --host <host>)] [(-p <port> | --port <port>)]\n" +
+                      "                [(-pp | --print-port)] [(-pw <password> | --password <password>)]\n" +
+                      "                [(-pwf <passwordFilePath> | --password-file <passwordFilePath>)]\n" +
+                      "                [(-u <username> | --username <username>)] reloadcidrgroupscache\n" +
+                      "\n" +
+                      "OPTIONS\n" +
+                      "        -h <host>, --host <host>\n" +
+                      "            Node hostname or ip address\n" +
+                      "\n" +
+                      "        -p <port>, --port <port>\n" +
+                      "            Remote jmx agent port number\n" +
+                      "\n" +
+                      "        -pp, --print-port\n" +
+                      "            Operate in 4.0 mode with hosts disambiguated by port number\n" +
+                      "\n" +
+                      "        -pw <password>, --password <password>\n" +
+                      "            Remote jmx agent password\n" +
+                      "\n" +
+                      "        -pwf <passwordFilePath>, --password-file <passwordFilePath>\n" +
+                      "            Path to the JMX password file\n" +
+                      "\n" +
+                      "        -u <username>, --username <username>\n" +
+                      "            Remote jmx agent username\n" +
+                      "\n" +
+                      "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    private long getCidrGroupsReadCount()
+    {
+        return DatabaseDescriptor.getCIDRAuthorizer().getCidrAuthorizerMetrics().cacheReloadCount.getCount();
+    }
+
+    @Test
+    public void testReloadCidrGroupsCache()
+    {
+        AuthenticatedUser role = new AuthenticatedUser(ROLE_A.getRoleName());
+
+        long originalReadsCount = getCidrGroupsReadCount();
+
+        // ensure cidr groups cache not reloaded
+        role.hasAccessFromIp(ipAddr);
+        assertThat(originalReadsCount).isEqualTo(getCidrGroupsReadCount());
+
+        // invalidate all, which reloads cidr groups cache
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("reloadcidrgroupscache");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("Reloaded CIDR groups cache");
+
+        // ensure cidr groups cache reloaded
+        role.hasAccessFromIp(ipAddr);
+        assertThat(originalReadsCount).isLessThan(getCidrGroupsReadCount());
+    }
+}
diff --git a/test/unit/org/apache/cassandra/tools/nodetool/UpdateCIDRGroupTest.java b/test/unit/org/apache/cassandra/tools/nodetool/UpdateCIDRGroupTest.java
new file mode 100644
index 0000000000..b3fbb9d34d
--- /dev/null
+++ b/test/unit/org/apache/cassandra/tools/nodetool/UpdateCIDRGroupTest.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.tools.nodetool;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.auth.AuthCacheService;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.tools.ToolRunner;
+
+import static java.lang.String.format;
+import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS;
+import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UpdateCIDRGroupTest extends CQLTester
+{
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        CQLTester.setUpClass();
+        CQLTester.requireAuthentication();
+        AuthCacheService.initializeAndRegisterCaches();
+
+        startJMXServer();
+    }
+
+    @Before
+    public void cleanCidrGroups() throws Throwable
+    {
+        execute(format("TRUNCATE %s.%s", AUTH_KEYSPACE_NAME, CIDR_GROUPS));
+    }
+
+    @Test
+    @SuppressWarnings("SingleCharacterStringConcatenation")
+    public void testUpdateCidrGroupDoc()
+    {
+        // If you added, modified options or help, please update docs if necessary
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", "updatecidrgroup");
+        tool.assertOnCleanExit();
+
+        String help = "NAME\n" +
+                      "        nodetool updatecidrgroup - Insert/Update a cidr group\n" +
+                      "\n" +
+                      "SYNOPSIS\n" +
+                      "        nodetool [(-h <host> | --host <host>)] [(-p <port> | --port <port>)]\n" +
+                      "                [(-pp | --print-port)] [(-pw <password> | --password <password>)]\n" +
+                      "                [(-pwf <passwordFilePath> | --password-file <passwordFilePath>)]\n" +
+                      "                [(-u <username> | --username <username>)] updatecidrgroup [--]\n" +
+                      "                [<cidrGroup> <cidr> ...]\n" +
+                      "\n" +
+                      "OPTIONS\n" +
+                      "        -h <host>, --host <host>\n" +
+                      "            Node hostname or ip address\n" +
+                      "\n" +
+                      "        -p <port>, --port <port>\n" +
+                      "            Remote jmx agent port number\n" +
+                      "\n" +
+                      "        -pp, --print-port\n" +
+                      "            Operate in 4.0 mode with hosts disambiguated by port number\n" +
+                      "\n" +
+                      "        -pw <password>, --password <password>\n" +
+                      "            Remote jmx agent password\n" +
+                      "\n" +
+                      "        -pwf <passwordFilePath>, --password-file <passwordFilePath>\n" +
+                      "            Path to the JMX password file\n" +
+                      "\n" +
+                      "        -u <username>, --username <username>\n" +
+                      "            Remote jmx agent username\n" +
+                      "\n" +
+                      "        --\n" +
+                      "            This option can be used to separate command-line options from the\n" +
+                      "            list of argument, (useful when arguments might be mistaken for\n" +
+                      "            command-line options\n" +
+                      "\n" +
+                      "        [<cidrGroup> <cidr> ...]\n" +
+                      "            Requires a cidr group name, followed by one or more CIDRs separated\n" +
+                      "            by space\n" +
+                      "\n" +
+                      "\n";
+        assertThat(tool.getStdout()).isEqualTo(help);
+    }
+
+    @Test
+    public void testUpdateCidrGroup()
+    {
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("listcidrgroups");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        tool = ToolRunner.invokeNodetool("updatecidrgroup", "test1", "10.11.12.0/24");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        tool = ToolRunner.invokeNodetool("updatecidrgroup", "test2", "11.11.12.0/24", "12.11.0.0/18");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        tool = ToolRunner.invokeNodetool("listcidrgroups");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("test1");
+        assertThat(tool.getStdout()).contains("test2");
+
+        tool = ToolRunner.invokeNodetool("listcidrgroups", "test1");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("10.11.12.0/24");
+
+        tool = ToolRunner.invokeNodetool("listcidrgroups", "test2");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("11.11.12.0/24");
+        assertThat(tool.getStdout()).contains("12.11.0.0/18");
+    }
+
+    @Test
+    public void testUpdateSameCidrGroup()
+    {
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("listcidrgroups");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        tool = ToolRunner.invokeNodetool("updatecidrgroup", "test", "10.11.12.0/24");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        tool = ToolRunner.invokeNodetool("listcidrgroups", "test");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("10.11.12.0/24");
+
+        tool = ToolRunner.invokeNodetool("updatecidrgroup", "test", "11.11.12.0/24");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+
+        tool = ToolRunner.invokeNodetool("listcidrgroups", "test");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).contains("11.11.12.0/24");
+    }
+
+    @Test
+    public void testUpdateInvalidCidrGroup()
+    {
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("updatecidrgroup", "test", "10.11.12.0/33");
+        assertThat(tool.getStderr()).contains("is not a valid CIDR String");
+
+        tool = ToolRunner.invokeNodetool("listcidrgroups", "test");
+        tool.assertOnCleanExit();
+        assertThat(tool.getStdout()).isEmpty();
+    }
+}
diff --git a/test/unit/org/apache/cassandra/tools/nodetool/VerifyTest.java b/test/unit/org/apache/cassandra/tools/nodetool/VerifyTest.java
index 47ca5febf2..1dc7a30e26 100644
--- a/test/unit/org/apache/cassandra/tools/nodetool/VerifyTest.java
+++ b/test/unit/org/apache/cassandra/tools/nodetool/VerifyTest.java
@@ -41,7 +41,8 @@ public class VerifyTest extends CQLTester
         SchemaLoader.setupAuth(roleManager,
                                new AuthTestUtils.LocalPasswordAuthenticator(),
                                new AuthTestUtils.LocalCassandraAuthorizer(),
-                               new AuthTestUtils.LocalCassandraNetworkAuthorizer());
+                               new AuthTestUtils.LocalCassandraNetworkAuthorizer(),
+                               new AuthTestUtils.LocalCassandraCIDRAuthorizer());
 
         roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_A, AuthTestUtils.getLoginRoleOptions());
         roleManager.createRole(AuthenticatedUser.SYSTEM_USER, ROLE_B, AuthTestUtils.getLoginRoleOptions());


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org