diff --git a/scripts/null_route_helper b/scripts/null_route_helper
new file mode 100755
index 000000000000..2813ff5971b3
--- /dev/null
+++ b/scripts/null_route_helper
@@ -0,0 +1,267 @@
+#!/usr/bin/env python3
+
+"""
+Utility for blocking and unblocking traffic from given source ip address on ACL tables.
+
+The block operation will insert a DENY rule at the top of the table. The unblock operation
+will remove an existing DENY rule that has been created by the block operation (i.e. it does
+NOT insert an ALLOW rule, only removes DENY rules).
+
+Since SONiC supports multi ACL rules share the same priority, all ACL rules created by null_route_helper will
+use the highest priority(9999).
+
+Example:
+
+Block traffic from 10.2.3.4:
+./null_route_helper block acl_table_name 10.2.3.4
+
+Unblock all traffic from 10.2.3.4:
+./null_route_helper unblock acl_table_name 10.2.3.4
+
+List all acl rules added by this script
+./null_route_helper list acl_table_name
+"""
+
+
+from __future__ import print_function
+
+import syslog
+import sys
+import click
+import ipaddress
+import tabulate
+
+from swsscommon.swsscommon import ConfigDBConnector
+
+
+CONFIG_DB_ACL_TABLE_TABLE = "ACL_TABLE"
+CONFIG_DB_ACL_RULE_TABLE = "ACL_RULE"
+CONFIG_DB_VLAN_TABLE = "VLAN"
+
+ACTION_ALLOW = "FORWARD"
+ACTION_DENY = "DROP"
+ACTION_LIST = "LIST"
+
+# Since SONiC supports multi ACL rules share the same priority, we use 9999 (the highest) for all rules
+ACL_RULE_PRIORITY = 9999
+# The key of rule will be overridden with BLOCK_RULE_ + ip
+ACL_RULE_PREFIX = 'BLOCK_RULE_'
+
+# Internet Protocol version 4 EtherType
+ETHER_TYPE_IPV4 = 0x0800
+
+def notice(msg):
+    """
+    Log a NOTICE message to the console and syslog
+    """
+    syslog.syslog(syslog.LOG_NOTICE, msg)
+    print(msg)
+
+
+def error(msg):
+    """
+    Log an ERR message to the console and syslog, and exit the program with an error code
+    """
+    syslog.syslog(syslog.LOG_ERR, msg)
+    print(msg, file=sys.stderr)
+    sys.exit(1)
+
+
+def ip_ver(ip_prefix):
+    return ipaddress.ip_network(ip_prefix, False).version
+
+
+def confirm_required_table_existence(configdb, sub_table_name):
+    """
+    Check the existence of required ACL table, and exit if absent 
+    """
+    target_table = configdb.get_entry(CONFIG_DB_ACL_TABLE_TABLE, sub_table_name)
+
+    if not target_table:
+        error("Table {} not found, exiting...".format(sub_table_name))
+    
+    return True
+
+
+def get_acl_rule_key(ip_prefix):
+    """
+    Get the key that will be used to refer to the ACL rule used to block traffic from a source ip.
+    Since the rules are all given the same priority in SONiC, we can't identify a rule based on the priority.
+    So, we use the destination IP being blocked to give each rule a unique name in the system.
+    """
+    return ACL_RULE_PREFIX + str(ip_prefix)
+
+
+def get_all_acl_rules(configdb, table_name):
+    """
+    Return a dict of existed acl rules
+    {(u'NULL_ROUTE_TABLE', u'BLOCK_RULE_1.1.1.1/32'): {'PRIORITY': '9999', 'PACKET_ACTION': 'FORWARD', 'SRC_IP': '1.1.1.1/32'},...}
+    """
+    key = CONFIG_DB_ACL_RULE_TABLE + '|' + table_name
+    all_rules = configdb.get_table(key)
+    block_rules = {}
+    for k, v in all_rules.items():
+        if k[1].startswith(ACL_RULE_PREFIX):
+            block_rules[k] = v
+
+    return block_rules
+
+
+def validate_input(ip_address):
+    """
+    Validate the format of input
+    """
+    try:
+        ip_n = ipaddress.ip_network(ip_address, False)
+        ver = ip_n.version
+        prefix_len = ip_n.prefixlen
+        # Prefix len must be 32 for IPV4 and 128 for IPV6 
+        if ver == 4 and prefix_len == 32 or ver == 6 and prefix_len == 128:
+            return ip_n.with_prefixlen
+        
+        error("Prefix length must be 32 (IPv4) or 128 (IPv6)")
+    except ValueError as e:
+        error("Could not parse {} as a valid IP address; exception={}".format(ip_address, e))
+
+
+def build_acl_rule(priority, src_ip):
+    """
+    Bild DROP rule for given src_ip and priority
+    """
+    rule = {
+        "PRIORITY": str(priority),
+        "PACKET_ACTION": "DROP"
+    }
+    if ip_ver(src_ip) == 4:
+        rule['ETHER_TYPE'] = str(ETHER_TYPE_IPV4)
+        rule['SRC_IP'] = src_ip
+    else:
+        rule['IP_TYPE'] = 'IPV6ANY'
+        rule['SRC_IPV6'] = src_ip
+
+    return rule
+
+
+def get_rule(configdb, table_name, ip_prefix):
+    """
+    Get Acl rule for given ip_prefix
+    """
+    key_name = 'SRC_IP' if ip_ver(ip_prefix) == 4 else 'SRC_IPV6'
+    all_rules = get_all_acl_rules(configdb, table_name)
+    for key, rule in all_rules.items():
+        if ip_prefix == rule.get(key_name, None):
+            if ip_prefix:
+                return {key: rule}
+
+    return None
+
+
+def update_acl_table(configdb, acl_table_name, ip_prefix, action):
+    """
+    Update ACL table to apply new rules for given ip_prefix. 'action' is supposed to be in ['DENY', 'ALLOW']
+    For 'DENY', an 'DROP' rule for given ip_prefix will be added if not existed
+    For 'ALLOW', we will try to remove the existing 'DENY' rule, and nothing is changed if not existed
+    """
+    confirm_required_table_existence(configdb, acl_table_name)
+    rule = get_rule(configdb, acl_table_name, ip_prefix)
+    rule_key = list(rule.keys())[0] if rule else None
+    rule_value = list(rule.values())[0] if rule else None
+    if action == ACTION_ALLOW:
+        if not rule:
+            return
+        # Delete existing BLOCK rule for given ip_prefix
+        # Pass None as data will delete the entry
+        configdb.mod_entry(CONFIG_DB_ACL_RULE_TABLE, rule_key, None)
+    else:
+        if rule:
+            if rule_value['PACKET_ACTION'] == 'DROP':
+                return
+            else:
+                # If there is 'FORWARDED' ACL rule, then change it to 'DROP'
+                rule_value['PACKET_ACTION'] = 'DROP'
+                configdb.mod_entry(CONFIG_DB_ACL_RULE_TABLE, rule_key, rule_value)
+        else:
+            priority = ACL_RULE_PRIORITY
+            new_rule_key = (acl_table_name, get_acl_rule_key(ip_prefix))
+            new_rule_value = build_acl_rule(priority, ip_prefix)
+            configdb.set_entry(CONFIG_DB_ACL_RULE_TABLE, new_rule_key, new_rule_value)
+
+
+def list_all_null_route_rules(configdb, table_name):
+    """
+    List all rules added by this script
+    """
+    
+    confirm_required_table_existence(configdb, table_name)
+    header = ("Table", "Rule", "Priority", "Action", "Match")
+    all_rules = get_all_acl_rules(configdb, table_name)
+
+    match_keys = ["SRC_IP", "SRC_IPV6"]
+    data = []
+    for (_, rule_id), rule in all_rules.items():
+        priority = rule.get("PRIORITY", "N/A")
+        action = rule.get("PACKET_ACTION", "N/A")
+        match = "N/A"
+        for k in match_keys:
+            if k in rule:
+                match = rule[k]
+                break
+
+        data.append([table_name, rule_id, priority, action, match])
+
+    print(tabulate.tabulate(data, headers=header, tablefmt="simple", missingval=""))
+
+
+def null_route_helper(table_name, action, ip_prefix=None):
+    """
+    Helper function called by 'click'.
+    """
+    configdb = ConfigDBConnector()
+    configdb.connect()
+    if action == ACTION_LIST:
+        list_all_null_route_rules(configdb, table_name)
+    else:
+        ip_prefix = validate_input(ip_prefix)
+        update_acl_table(configdb, table_name, ip_prefix, action)
+
+
+@click.group()
+def cli():
+    pass
+
+
+# ./null_route_helper block table_name 1.2.3.4
+@cli.command('block')
+@click.argument("table_name", type=click.STRING, required=True)
+@click.argument("ip_prefix", type=click.STRING, required=True)
+def block(table_name, ip_prefix):
+    """
+    Block traffic from given src ip prefix
+    """
+    null_route_helper(table_name, ACTION_DENY, ip_prefix)
+
+
+# ./null_route_helper unblock table_name 1.2.3.4
+@cli.command('unblock')
+@click.argument("table_name", type=click.STRING, required=True)
+@click.argument("ip_prefix", type=click.STRING, required=True)
+def unblock(table_name, ip_prefix):
+    """
+    Unblock traffic from given src ip prefix
+    """
+    null_route_helper(table_name, ACTION_ALLOW, ip_prefix)
+
+
+# ./null_route_helper list table_name
+@cli.command('list')
+@click.argument("table_name", type=click.STRING, required=True)
+def list_rules(table_name):
+    """
+    List all rules *added by this script*
+    """
+    null_route_helper(table_name, ACTION_LIST)
+
+
+if __name__ == "__main__":
+    cli()
+
diff --git a/setup.py b/setup.py
index 6c8a349c69c8..5847b6e6eedc 100644
--- a/setup.py
+++ b/setup.py
@@ -130,7 +130,8 @@
         'scripts/watermarkstat',
         'scripts/watermarkcfg',
         'scripts/sonic-kdump-config',
-        'scripts/centralize_database'
+        'scripts/centralize_database',
+        'scripts/null_route_helper'
     ],
     entry_points={
         'console_scripts': [
diff --git a/tests/aclshow_test.py b/tests/aclshow_test.py
index e41d56b9eb0c..9529be36891f 100644
--- a/tests/aclshow_test.py
+++ b/tests/aclshow_test.py
@@ -35,19 +35,27 @@
 
 # Expected output for aclshow -a
 all_output = """\
-RULE NAME     TABLE NAME      PRIO    PACKETS COUNT    BYTES COUNT
-------------  ------------  ------  ---------------  -------------
-RULE_1        DATAACL         9999              101            100
-RULE_2        DATAACL         9998              201            200
-RULE_3        DATAACL         9997              301            300
-RULE_4        DATAACL         9996              401            400
-RULE_05       DATAACL         9995                0              0
-RULE_7        DATAACL         9993              701            700
-RULE_9        DATAACL         9991              901            900
-RULE_10       DATAACL         9989             1001           1000
-DEFAULT_RULE  DATAACL            1                2              1
-RULE_6        EVERFLOW        9994              601            600
-RULE_08       EVERFLOW        9992                0              0
+RULE NAME                              TABLE NAME       PRIO  PACKETS COUNT    BYTES COUNT
+-------------------------------------  -------------  ------  ---------------  -------------
+RULE_1                                 DATAACL          9999  101              100
+RULE_2                                 DATAACL          9998  201              200
+RULE_3                                 DATAACL          9997  301              300
+RULE_4                                 DATAACL          9996  401              400
+RULE_05                                DATAACL          9995  0                0
+RULE_7                                 DATAACL          9993  701              700
+RULE_9                                 DATAACL          9991  901              900
+RULE_10                                DATAACL          9989  1001             1000
+DEFAULT_RULE                           DATAACL             1  2                1
+RULE_6                                 EVERFLOW         9994  601              600
+RULE_08                                EVERFLOW         9992  0                0
+RULE_1                                 NULL_ROUTE_V4    9999  N/A              N/A
+BLOCK_RULE_10.0.0.2/32                 NULL_ROUTE_V4    9999  N/A              N/A
+BLOCK_RULE_10.0.0.3/32                 NULL_ROUTE_V4    9999  N/A              N/A
+DEFAULT_RULE                           NULL_ROUTE_V4       1  N/A              N/A
+RULE_1                                 NULL_ROUTE_V6    9999  N/A              N/A
+BLOCK_RULE_1000:1000:1000:1000::2/128  NULL_ROUTE_V6    9999  N/A              N/A
+BLOCK_RULE_1000:1000:1000:1000::3/128  NULL_ROUTE_V6    9999  N/A              N/A
+DEFAULT_RULE                           NULL_ROUTE_V6       1  N/A              N/A
 """
 
 # Expected output for aclshow -r RULE_1 -t DATAACL
@@ -80,8 +88,8 @@
 # Expected output for aclshow -r RULE_4,RULE_6 -vv
 rule4_rule6_verbose_output = """\
 Reading ACL info...
-Total number of ACL Tables: 8
-Total number of ACL Rules: 11
+Total number of ACL Tables: 10
+Total number of ACL Rules: 19
 
 RULE NAME    TABLE NAME      PRIO    PACKETS COUNT    BYTES COUNT
 -----------  ------------  ------  ---------------  -------------
@@ -116,19 +124,27 @@
 # Expected output for
 # aclshow -a -c ; aclshow -a
 all_after_clear_output = """\
-RULE NAME     TABLE NAME      PRIO    PACKETS COUNT    BYTES COUNT
-------------  ------------  ------  ---------------  -------------
-RULE_1        DATAACL         9999                0              0
-RULE_2        DATAACL         9998                0              0
-RULE_3        DATAACL         9997                0              0
-RULE_4        DATAACL         9996                0              0
-RULE_05       DATAACL         9995                0              0
-RULE_7        DATAACL         9993                0              0
-RULE_9        DATAACL         9991                0              0
-RULE_10       DATAACL         9989                0              0
-DEFAULT_RULE  DATAACL            1                0              0
-RULE_6        EVERFLOW        9994                0              0
-RULE_08       EVERFLOW        9992                0              0
+RULE NAME                              TABLE NAME       PRIO  PACKETS COUNT    BYTES COUNT
+-------------------------------------  -------------  ------  ---------------  -------------
+RULE_1                                 DATAACL          9999  0                0
+RULE_2                                 DATAACL          9998  0                0
+RULE_3                                 DATAACL          9997  0                0
+RULE_4                                 DATAACL          9996  0                0
+RULE_05                                DATAACL          9995  0                0
+RULE_7                                 DATAACL          9993  0                0
+RULE_9                                 DATAACL          9991  0                0
+RULE_10                                DATAACL          9989  0                0
+DEFAULT_RULE                           DATAACL             1  0                0
+RULE_6                                 EVERFLOW         9994  0                0
+RULE_08                                EVERFLOW         9992  0                0
+RULE_1                                 NULL_ROUTE_V4    9999  N/A              N/A
+BLOCK_RULE_10.0.0.2/32                 NULL_ROUTE_V4    9999  N/A              N/A
+BLOCK_RULE_10.0.0.3/32                 NULL_ROUTE_V4    9999  N/A              N/A
+DEFAULT_RULE                           NULL_ROUTE_V4       1  N/A              N/A
+RULE_1                                 NULL_ROUTE_V6    9999  N/A              N/A
+BLOCK_RULE_1000:1000:1000:1000::2/128  NULL_ROUTE_V6    9999  N/A              N/A
+BLOCK_RULE_1000:1000:1000:1000::3/128  NULL_ROUTE_V6    9999  N/A              N/A
+DEFAULT_RULE                           NULL_ROUTE_V6       1  N/A              N/A
 """
 
 
diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json
index 430fe3b16285..4c60cf992a2e 100644
--- a/tests/mock_tables/config_db.json
+++ b/tests/mock_tables/config_db.json
@@ -373,6 +373,44 @@
     "VLAN_SUB_INTERFACE|Ethernet0.10": {
         "admin_status": "up"
     },
+    "ACL_RULE|NULL_ROUTE_V4|DEFAULT_RULE": {
+        "PACKET_ACTION": "DROP",
+        "PRIORITY": "1"
+    },
+    "ACL_RULE|NULL_ROUTE_V4|RULE_1": {
+        "PACKET_ACTION": "DROP",
+        "PRIORITY": "9999",
+        "SRC_IP": "10.0.0.1/32"
+    },
+    "ACL_RULE|NULL_ROUTE_V4|BLOCK_RULE_10.0.0.2/32": {
+        "PACKET_ACTION": "DROP",
+        "PRIORITY": "9999",
+        "SRC_IP": "10.0.0.2/32"
+    },
+    "ACL_RULE|NULL_ROUTE_V4|BLOCK_RULE_10.0.0.3/32": {
+        "PACKET_ACTION": "FORWARD",
+        "PRIORITY": "9999",
+        "SRC_IP": "10.0.0.3/32"
+    },
+    "ACL_RULE|NULL_ROUTE_V6|DEFAULT_RULE": {
+        "PACKET_ACTION": "DROP",
+        "PRIORITY": "1"
+    },
+    "ACL_RULE|NULL_ROUTE_V6|RULE_1": {
+        "PACKET_ACTION": "DROP",
+        "PRIORITY": "9999",
+        "SRC_IPV6": "1000:1000:1000:1000::1/128"
+    },
+    "ACL_RULE|NULL_ROUTE_V6|BLOCK_RULE_1000:1000:1000:1000::2/128": {
+        "PACKET_ACTION": "DROP",
+        "PRIORITY": "9999",
+        "SRC_IPV6":"1000:1000:1000:1000::2/128"
+    },
+    "ACL_RULE|NULL_ROUTE_V6|BLOCK_RULE_1000:1000:1000:1000::3/128": {
+        "PACKET_ACTION": "FORWARD",
+        "PRIORITY": "9999",
+        "SRC_IPV6":"1000:1000:1000:1000::3/128"
+    },
     "ACL_RULE|DATAACL|DEFAULT_RULE": {
         "PACKET_ACTION": "DROP",
         "PRIORITY": "1"
@@ -427,6 +465,16 @@
         "priority": "9989",
         "SRC_IP": "10.0.0.3/32"
     },
+    "ACL_TABLE|NULL_ROUTE_V4": {
+        "policy_desc": "DATAACL",
+        "ports@": "PortChannel0002,PortChannel0005,PortChannel0008,PortChannel0011,PortChannel0014,PortChannel0017,PortChannel0020,PortChannel0023",
+        "type": "L3"
+    },
+    "ACL_TABLE|NULL_ROUTE_V6": {
+        "policy_desc": "DATAACL",
+        "ports@": "PortChannel0002,PortChannel0005,PortChannel0008,PortChannel0011,PortChannel0014,PortChannel0017,PortChannel0020,PortChannel0023",
+        "type": "L3V6"
+    },
     "ACL_TABLE|DATAACL": {
         "policy_desc": "DATAACL",
         "ports@": "PortChannel0002,PortChannel0005,PortChannel0008,PortChannel0011,PortChannel0014,PortChannel0017,PortChannel0020,PortChannel0023,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124",
diff --git a/tests/null_route_helper_test.py b/tests/null_route_helper_test.py
new file mode 100644
index 000000000000..f07a981aa356
--- /dev/null
+++ b/tests/null_route_helper_test.py
@@ -0,0 +1,206 @@
+import pytest
+import os
+import imp
+
+from click.testing import CliRunner
+from swsssdk import ConfigDBConnector
+
+null_route_helper = imp.load_source('null_route_helper', os.path.join(os.path.dirname(__file__), '..', 'scripts','null_route_helper'))
+null_route_helper.ConfigDBConnector = ConfigDBConnector
+
+expected_stdout_v4 = "" + \
+"""Table          Rule                      Priority  Action    Match
+-------------  ----------------------  ----------  --------  -----------
+NULL_ROUTE_V4  BLOCK_RULE_10.0.0.2/32        9999  DROP      10.0.0.2/32
+NULL_ROUTE_V4  BLOCK_RULE_10.0.0.3/32        9999  FORWARD   10.0.0.3/32
+"""
+
+expected_stdout_v6 = "" + \
+"""Table          Rule                                     Priority  Action    Match
+-------------  -------------------------------------  ----------  --------  --------------------------
+NULL_ROUTE_V6  BLOCK_RULE_1000:1000:1000:1000::2/128        9999  DROP      1000:1000:1000:1000::2/128
+NULL_ROUTE_V6  BLOCK_RULE_1000:1000:1000:1000::3/128        9999  FORWARD   1000:1000:1000:1000::3/128
+"""
+
+def test_ip_validation():
+    # Verify prefix len will be appended if not set
+    assert(null_route_helper.validate_input("1.2.3.4") == "1.2.3.4/32")
+    assert(null_route_helper.validate_input("::1") == "::1/128")
+
+    assert(null_route_helper.validate_input("1.2.3.4/32") == "1.2.3.4/32")
+
+    assert(null_route_helper.validate_input("1000:1000:1000:1000::1/128") == "1000:1000:1000:1000::1/128")
+
+    with pytest.raises(SystemExit) as e:
+        null_route_helper.validate_input("a.b.c.d")
+    assert(e.value.code != 0)
+
+    with pytest.raises(SystemExit) as e:
+        null_route_helper.validate_input("1.2.3.4/21/32")
+    assert(e.value.code != 0)
+
+    # Verify only 32 prefix len is accepted for IPv4
+    with pytest.raises(SystemExit) as e:
+        null_route_helper.validate_input("1.2.3.4/21")
+    assert(e.value.code != 0)
+
+    # Verify only 128 prefix len is accepted for IPv6
+    with pytest.raises(SystemExit) as e:
+        null_route_helper.validate_input("1000:1000:1000:1000::1/120")
+    assert(e.value.code != 0)
+
+
+def test_confirm_required_table_existence():
+    configdb = ConfigDBConnector()
+    configdb.connect()
+
+    assert(null_route_helper.confirm_required_table_existence(configdb, "NULL_ROUTE_V4"))
+    assert(null_route_helper.confirm_required_table_existence(configdb, "NULL_ROUTE_V6"))
+
+    with pytest.raises(SystemExit) as e:
+        null_route_helper.confirm_required_table_existence(configdb, "NULL_ROUTE_FAKE")
+    assert(e.value.code != 0)
+
+
+def test_build_rule():
+    expected_rule_v4 = {
+        "PRIORITY": "9999",
+        "PACKET_ACTION": "DROP",
+        "ETHER_TYPE": "2048",
+        "SRC_IP": "1.2.3.4/32"
+    }
+    expected_rule_v6 = {
+        "PRIORITY": "9999",
+        "PACKET_ACTION": "DROP",
+        "IP_TYPE": "IPV6ANY",
+        "SRC_IPV6": "1000:1000:1000:1000::1/128"
+    }
+
+    assert(null_route_helper.build_acl_rule(9999, "1.2.3.4/32") == expected_rule_v4)
+    assert(null_route_helper.build_acl_rule(9999, "1000:1000:1000:1000::1/128") == expected_rule_v6)
+
+
+def test_get_rule():
+    configdb = ConfigDBConnector()
+    configdb.connect()
+
+    assert(null_route_helper.get_rule(configdb, "NULL_ROUTE_ABSENT", "10.0.0.1/32") == None)
+
+    assert(null_route_helper.get_rule(configdb, "NULL_ROUTE_V4", "10.0.0.1/32") == None)
+    assert(null_route_helper.get_rule(configdb, "NULL_ROUTE_V4", "10.0.0.2/32"))
+
+    assert(null_route_helper.get_rule(configdb, "NULL_ROUTE_V6", "1000:1000:1000:1000::1/128") == None)
+    assert(null_route_helper.get_rule(configdb, "NULL_ROUTE_V6", "1000:1000:1000:1000::2/128"))
+
+
+def test_run_when_table_absent():
+    runner = CliRunner()
+
+    result = runner.invoke(null_route_helper.cli.commands['block'], ['TABLE_ABSENT', '1.2.3.4'])
+    assert(result.exit_code != 0)
+    assert("not found" in result.output)
+
+    result = runner.invoke(null_route_helper.cli.commands['unblock'], ['TABLE_ABSENT', '1.2.3.4'])
+    assert(result.exit_code != 0)
+    assert("not found" in result.output)
+
+
+def test_run_with_invalid_ip():
+    runner = CliRunner()
+
+    result = runner.invoke(null_route_helper.cli.commands['block'], ['NULL_ROUTE_V4', 'a.b.c.d'])
+    assert(result.exit_code != 0)
+    assert("as a valid IP address" in result.output)
+
+    result = runner.invoke(null_route_helper.cli.commands['block'], ['NULL_ROUTE_V6', 'xx:xx:xx:xx'])
+    assert(result.exit_code != 0)
+    assert("as a valid IP address" in result.output)
+
+    result = runner.invoke(null_route_helper.cli.commands['unblock'], ['NULL_ROUTE_V4', 'a.b.c.d'])
+    assert(result.exit_code != 0)
+    assert("as a valid IP address" in result.output)
+
+    result = runner.invoke(null_route_helper.cli.commands['unblock'], ['NULL_ROUTE_V6', 'xx:xx:xx:xx'])
+    assert(result.exit_code != 0)
+    assert("as a valid IP address" in result.output)
+
+    result = runner.invoke(null_route_helper.cli.commands['block'], ['NULL_ROUTE_V4', '1.2.3.4/21'])
+    assert(result.exit_code != 0)
+    assert("Prefix length must be" in result.output)
+
+    result = runner.invoke(null_route_helper.cli.commands['block'], ['NULL_ROUTE_V6', '::1/120'])
+    assert(result.exit_code != 0)
+    assert("Prefix length must be" in result.output)
+    
+
+def test_block():
+    runner = CliRunner()
+
+    # Verify block ip that is already blocked
+    result = runner.invoke(null_route_helper.cli.commands['block'], ['NULL_ROUTE_V4', '10.0.0.2/32'])
+    assert(result.exit_code == 0)
+
+    # Verify block ip that is marked as forward
+    result = runner.invoke(null_route_helper.cli.commands['block'], ['NULL_ROUTE_V4', '10.0.0.3/32'])
+    assert(result.exit_code == 0)
+
+    # Verify unblock ip that is not present in any rule
+    result = runner.invoke(null_route_helper.cli.commands['block'], ['NULL_ROUTE_V4', '10.0.0.4/32'])
+    assert(result.exit_code == 0)
+
+    # Verify block ipv6 that is already blocked
+    result = runner.invoke(null_route_helper.cli.commands['block'], ['NULL_ROUTE_V6', '1000:1000:1000:1000::2/128'])
+    assert(result.exit_code == 0)
+
+    # Verify block ipv6 that is marked as forward
+    result = runner.invoke(null_route_helper.cli.commands['block'], ['NULL_ROUTE_V6', '1000:1000:1000:1000::3/128'])
+    assert(result.exit_code == 0)
+
+    # Verify block ipv6 that is not present in any rule
+    result = runner.invoke(null_route_helper.cli.commands['block'], ['NULL_ROUTE_V6', '1000:1000:1000:1000::4/128'])
+    assert(result.exit_code == 0)
+
+
+def test_unblock():
+    runner = CliRunner()
+
+    # Verify unblock ip that is blocked
+    result = runner.invoke(null_route_helper.cli.commands['unblock'], ['NULL_ROUTE_V4', '10.0.0.2/32'])
+    assert(result.exit_code == 0)
+
+    # Verify unblock ip that is not blocked
+    result = runner.invoke(null_route_helper.cli.commands['unblock'], ['NULL_ROUTE_V4', '10.0.0.3/32'])
+    assert(result.exit_code == 0)
+
+    # Verify unblock ip that is not present in any rule
+    result = runner.invoke(null_route_helper.cli.commands['unblock'], ['NULL_ROUTE_V4', '10.0.0.4/32'])
+    assert(result.exit_code == 0)
+
+    # Verify unblock ipv6 that is blocked
+    result = runner.invoke(null_route_helper.cli.commands['unblock'], ['NULL_ROUTE_V6', '1000:1000:1000:1000::2/128'])
+    assert(result.exit_code == 0)
+
+    # Verify unblock ipv6 that is marked as forward
+    result = runner.invoke(null_route_helper.cli.commands['unblock'], ['NULL_ROUTE_V6', '1000:1000:1000:1000::3/128'])
+    assert(result.exit_code == 0)
+
+    # Verify unblock ipv6 that is not present in any rule
+    result = runner.invoke(null_route_helper.cli.commands['unblock'], ['NULL_ROUTE_V6', '1000:1000:1000:1000::4/128'])
+    assert(result.exit_code == 0)
+
+
+def test_list():
+    runner = CliRunner()
+
+    # Verify list rules in non-existing table
+    result = runner.invoke(null_route_helper.cli.commands['list'], ['FAKE_NULL_ROUTE_V4'])
+    assert(result.exit_code != 0)
+
+    # Verify show IPv4 rules
+    result = runner.invoke(null_route_helper.cli.commands['list'], ['NULL_ROUTE_V4'])
+    assert(result.stdout == expected_stdout_v4)
+
+    # Verify show IPv6 rules
+    result = runner.invoke(null_route_helper.cli.commands['list'], ['NULL_ROUTE_V6'])
+    assert(result.stdout == expected_stdout_v6)
+