diff --git a/interface-definitions/include/interface/inbound-interface.xml.i b/interface-definitions/include/interface/inbound-interface.xml.i
new file mode 100644
index 000000000..5a8d47280
--- /dev/null
+++ b/interface-definitions/include/interface/inbound-interface.xml.i
@@ -0,0 +1,10 @@
+<!-- include start from interface/inbound-interface.xml.i -->
+<leafNode name="inbound-interface">
+  <properties>
+  <help>Inbound Interface</help>
+  <completionHelp>
+    <script>${vyos_completion_dir}/list_interfaces.py</script>
+  </completionHelp>
+  </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in
index 3769c3748..573a7963f 100644
--- a/interface-definitions/policy-local-route.xml.in
+++ b/interface-definitions/policy-local-route.xml.in
@@ -1,67 +1,188 @@
 <?xml version="1.0"?>
 <!-- Policy local-route -->
 <interfaceDefinition>
   <node name="policy">
     <children>
       <node name="local-route" owner="${vyos_conf_scripts_dir}/policy-local-route.py">
         <properties>
           <help>IPv4 policy route of local traffic</help>
         </properties>
         <children>
           <tagNode name="rule">
             <properties>
               <help>Policy local-route rule set number</help>
               <valueHelp>
                 <!-- table main with prio 32766 -->
                 <format>u32:1-32765</format>
-                <description>Local-route rule number (1-219)</description>
+                <description>Local-route rule number (1-32765)</description>
               </valueHelp>
               <constraint>
                 <validator name="numeric" argument="--range 1-32765"/>
               </constraint>
             </properties>
             <children>
               <node name="set">
                 <properties>
                   <help>Packet modifications</help>
                 </properties>
                 <children>
                   <leafNode name="table">
                     <properties>
                       <help>Routing table to forward packet with</help>
                       <valueHelp>
                         <format>u32:1-200</format>
                         <description>Table number</description>
                       </valueHelp>
                       <completionHelp>
                         <list>main</list>
                       </completionHelp>
                     </properties>
                   </leafNode>
                 </children>
               </node>
+              <leafNode name="fwmark">
+                <properties>
+                  <help>Match fwmark value</help>
+                  <valueHelp>
+                    <format>u32:1-2147483647</format>
+                    <description>Address to match against</description>
+                  </valueHelp>
+                  <constraint>
+                    <validator name="numeric" argument="--range 1-2147483647"/>
+                  </constraint>
+                </properties>
+              </leafNode>
               <leafNode name="source">
                 <properties>
                   <help>Source address or prefix</help>
                   <valueHelp>
                     <format>ipv4</format>
                     <description>Address to match against</description>
                   </valueHelp>
                   <valueHelp>
                     <format>ipv4net</format>
                     <description>Prefix to match against</description>
                   </valueHelp>
                   <constraint>
                     <validator name="ipv4-address"/>
                     <validator name="ip-prefix"/>
                   </constraint>
                   <multi/>
                 </properties>
               </leafNode>
+              <leafNode name="destination">
+                <properties>
+                  <help>Destination address or prefix</help>
+                  <valueHelp>
+                    <format>ipv4</format>
+                    <description>Address to match against</description>
+                  </valueHelp>
+                  <valueHelp>
+                    <format>ipv4net</format>
+                    <description>Prefix to match against</description>
+                  </valueHelp>
+                  <constraint>
+                    <validator name="ipv4-address"/>
+                    <validator name="ip-prefix"/>
+                  </constraint>
+                  <multi/>
+                </properties>
+              </leafNode>
+              #include <include/interface/inbound-interface.xml.i>
+            </children>
+          </tagNode>
+        </children>
+      </node>
+      <node name="local-route6" owner="${vyos_conf_scripts_dir}/policy-local-route.py">
+        <properties>
+          <help>IPv6 policy route of local traffic</help>
+        </properties>
+        <children>
+          <tagNode name="rule">
+            <properties>
+              <help>IPv6 policy local-route rule set number</help>
+              <valueHelp>
+                <!-- table main with prio 32766 -->
+                <format>u32:1-32765</format>
+                <description>Local-route rule number (1-32765)</description>
+              </valueHelp>
+              <constraint>
+                <validator name="numeric" argument="--range 1-32765"/>
+              </constraint>
+            </properties>
+            <children>
+              <node name="set">
+                <properties>
+                  <help>Packet modifications</help>
+                </properties>
+                <children>
+                  <leafNode name="table">
+                    <properties>
+                      <help>Routing table to forward packet with</help>
+                      <valueHelp>
+                        <format>u32:1-200</format>
+                        <description>Table number</description>
+                      </valueHelp>
+                      <completionHelp>
+                        <list>main</list>
+                      </completionHelp>
+                    </properties>
+                  </leafNode>
+                </children>
+              </node>
+              <leafNode name="fwmark">
+                <properties>
+                  <help>Match fwmark value</help>
+                  <valueHelp>
+                    <format>u32:1-2147483647</format>
+                    <description>Address to match against</description>
+                  </valueHelp>
+                  <constraint>
+                    <validator name="numeric" argument="--range 1-2147483647"/>
+                  </constraint>
+                </properties>
+              </leafNode>
+              <leafNode name="source">
+                <properties>
+                  <help>Source address or prefix</help>
+                  <valueHelp>
+                    <format>ipv4</format>
+                    <description>Address to match against</description>
+                  </valueHelp>
+                  <valueHelp>
+                    <format>ipv4net</format>
+                    <description>Prefix to match against</description>
+                  </valueHelp>
+                  <constraint>
+                    <validator name="ipv6-address"/>
+                    <validator name="ipv6-prefix"/>
+                  </constraint>
+                  <multi/>
+                </properties>
+              </leafNode>
+              <leafNode name="destination">
+                <properties>
+                  <help>Destination address or prefix</help>
+                  <valueHelp>
+                    <format>ipv6</format>
+                    <description>Address to match against</description>
+                  </valueHelp>
+                  <valueHelp>
+                    <format>ipv6net</format>
+                    <description>Prefix to match against</description>
+                  </valueHelp>
+                  <constraint>
+                    <validator name="ipv6-address"/>
+                    <validator name="ipv6-prefix"/>
+                  </constraint>
+                  <multi/>
+                </properties>
+              </leafNode>
+              #include <include/interface/inbound-interface.xml.i>
             </children>
           </tagNode>
         </children>
       </node>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index f1d195381..50f2d7b43 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -1,695 +1,1094 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import unittest
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 
 from vyos.configsession import ConfigSessionError
 from vyos.util import cmd
 
 base_path = ['policy']
 
 class TestPolicy(VyOSUnitTestSHIM.TestCase):
     def tearDown(self):
         self.cli_delete(base_path)
         self.cli_commit()
 
     def test_access_list(self):
         acls = {
             '50' : {
                 'rule' : {
                     '5' : {
                         'action' : 'permit',
                         'source' : { 'any' : '' },
                     },
                     '10' : {
                         'action' : 'deny',
                         'source' : { 'host' : '1.2.3.4' },
                     },
                 },
              },
             '150' : {
                 'rule' : {
                     '10' : {
                         'action' : 'permit',
                         'source' : { 'any' : '' },
                         'destination' : { 'host' : '2.2.2.2' },
                     },
                     '10' : {
                         'action' : 'deny',
                         'source' : { 'any' : '' },
                         'destination' : { 'any' : '' },
                     },
                 },
             },
             '2000' : {
                 'rule' : {
                     '10' : {
                         'action' : 'permit',
                         'destination' : { 'any' : '' },
                         'source' : { 'network' : '10.0.0.0', 'inverse-mask' : '0.255.255.255' },
                     },
                     '20' : {
                         'action' : 'permit',
                         'destination' : { 'any' : '' },
                         'source' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' },
                     },
                     '30' : {
                         'action' : 'permit',
                         'destination' : { 'any' : '' },
                         'source' : { 'network' : '192.168.0.0', 'inverse-mask' : '0.0.255.255' },
                     },
                     '50' : {
                         'action' : 'permit',
                         'destination' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' },
                         'source' : { 'network' : '10.0.0.0', 'inverse-mask' : '0.255.255.255' },
                     },
                     '60' : {
                         'action' : 'deny',
                         'destination' : { 'network' : '192.168.0.0', 'inverse-mask' : '0.0.255.255' },
                         'source' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' },
                     },
                     '70' : {
                         'action' : 'deny',
                         'destination' : { 'any' : '' },
                         'source' : { 'any' : '' },
                     },
                 },
             },
         }
 
         for acl, acl_config in acls.items():
             path = base_path + ['access-list', acl]
             self.cli_set(path + ['description', f'VyOS-ACL-{acl}'])
             if 'rule' not in acl_config:
                 continue
 
             for rule, rule_config in acl_config['rule'].items():
                 self.cli_set(path + ['rule', rule, 'action', rule_config['action']])
                 for direction in ['source', 'destination']:
                     if direction in rule_config:
                         if 'any' in rule_config[direction]:
                             self.cli_set(path + ['rule', rule, direction, 'any'])
                         if 'host' in rule_config[direction]:
                             self.cli_set(path + ['rule', rule, direction, 'host', rule_config[direction]['host']])
                         if 'network' in rule_config[direction]:
                             self.cli_set(path + ['rule', rule, direction, 'network', rule_config[direction]['network']])
                             self.cli_set(path + ['rule', rule, direction, 'inverse-mask', rule_config[direction]['inverse-mask']])
 
         self.cli_commit()
 
         config = self.getFRRconfig('access-list', end='')
         for acl, acl_config in acls.items():
             seq = '5'
             for rule, rule_config in acl_config['rule'].items():
                 tmp = f'access-list {acl} seq {seq}'
                 if rule_config['action'] == 'permit':
                     tmp += ' permit'
                 else:
                     tmp += ' deny'
 
                 if {'source', 'destination'} <= set(rule_config):
                     tmp += ' ip'
 
                 for direction in ['source', 'destination']:
                     if direction in rule_config:
                         if 'any' in rule_config[direction]:
                             tmp += ' any'
                         if 'host' in rule_config[direction]:
                             tmp += ' ' + rule_config[direction]['host']
                         if 'network' in rule_config[direction]:
                             tmp += ' ' + rule_config[direction]['network'] + ' ' + rule_config[direction]['inverse-mask']
 
                 self.assertIn(tmp, config)
                 seq = int(seq) + 5
 
     def test_access_list6(self):
         acls = {
             '50' : {
                 'rule' : {
                     '5' : {
                         'action' : 'permit',
                         'source' : { 'any' : '' },
                     },
                     '10' : {
                         'action' : 'deny',
                         'source' : { 'network' : '2001:db8:10::/48', 'exact-match' : '' },
                     },
                     '10' : {
                         'action' : 'deny',
                         'source' : { 'network' : '2001:db8:20::/48' },
                     },
                 },
              },
             '100' : {
                 'rule' : {
                     '5' : {
                         'action' : 'deny',
                         'source' : { 'network' : '2001:db8:10::/64', 'exact-match' : '' },
                     },
                     '10' : {
                         'action' : 'deny',
                         'source' : { 'network' : '2001:db8:20::/64', },
                     },
                     '15' : {
                         'action' : 'deny',
                         'source' : { 'network' : '2001:db8:30::/64', 'exact-match' : '' },
                     },
                     '20' : {
                         'action' : 'deny',
                         'source' : { 'network' : '2001:db8:40::/64', 'exact-match' : '' },
                     },
                     '100' : {
                         'action' : 'deny',
                         'source' : { 'any' : '' },
                     },
                 },
              },
         }
 
         for acl, acl_config in acls.items():
             path = base_path + ['access-list6', acl]
             self.cli_set(path + ['description', f'VyOS-ACL-{acl}'])
             if 'rule' not in acl_config:
                 continue
 
             for rule, rule_config in acl_config['rule'].items():
                 self.cli_set(path + ['rule', rule, 'action', rule_config['action']])
                 for direction in ['source', 'destination']:
                     if direction in rule_config:
                         if 'any' in rule_config[direction]:
                             self.cli_set(path + ['rule', rule, direction, 'any'])
                         if 'network' in rule_config[direction]:
                             self.cli_set(path + ['rule', rule, direction, 'network', rule_config[direction]['network']])
                         if 'exact-match' in rule_config[direction]:
                             self.cli_set(path + ['rule', rule, direction, 'exact-match'])
 
         self.cli_commit()
 
         config = self.getFRRconfig('ipv6 access-list', end='')
         for acl, acl_config in acls.items():
             seq = '5'
             for rule, rule_config in acl_config['rule'].items():
                 tmp = f'ipv6 access-list {acl} seq {seq}'
                 if rule_config['action'] == 'permit':
                     tmp += ' permit'
                 else:
                     tmp += ' deny'
 
                 if {'source', 'destination'} <= set(rule_config):
                     tmp += ' ip'
 
                 for direction in ['source', 'destination']:
                     if direction in rule_config:
                         if 'any' in rule_config[direction]:
                             tmp += ' any'
                         if 'network' in rule_config[direction]:
                             tmp += ' ' + rule_config[direction]['network']
                         if 'exact-match' in rule_config[direction]:
                             tmp += ' exact-match'
 
                 self.assertIn(tmp, config)
                 seq = int(seq) + 5
 
 
     def test_as_path_list(self):
         test_data = {
             'VyOS' : {
                 'rule' : {
                     '10' : {
                         'action' : 'permit',
                         'regex'  : '^44501 64502$',
                     },
                     '20' : {
                         'action' : 'permit',
                         'regex'  : '44501|44502|44503',
                     },
                     '30' : {
                         'action' : 'permit',
                         'regex'  : '^44501_([0-9]+_)+',
                     },
                 },
             },
             'Customers' : {
                 'rule' : {
                     '10' : {
                         'action' : 'permit',
                         'regex'  : '_10_',
                     },
                     '20' : {
                         'action' : 'permit',
                         'regex'  : '_20_',
                     },
                     '30' : {
                         'action' : 'permit',
                         'regex'  : '_30_',
                     },
                     '30' : {
                         'action' : 'deny',
                         'regex'  : '_40_',
                     },
                 },
             },
             'bogons' : {
                 'rule' : {
                     '10' : {
                         'action' : 'permit',
                         'regex'  : '_0_',
                     },
                     '20' : {
                         'action' : 'permit',
                         'regex'  : '_23456_',
                     },
                     '30' : {
                         'action' : 'permit',
                         'regex'  : '_6449[6-9]_|_65[0-4][0-9][0-9]_|_655[0-4][0-9]_|_6555[0-1]_',
                     },
                     '30' : {
                         'action' : 'permit',
                         'regex'  : '_6555[2-9]_|_655[6-9][0-9]_|_65[6-9][0-9][0-9]_|_6[6-9][0-9][0-9][0-]_|_[7-9][0-9][0-9][0-9][0-9]_|_1[0-2][0-9][0-9][0-9][0-9]_|_130[0-9][0-9][0-9]_|_1310[0-6][0-9]_|_13107[01]_',
                     },
                 },
             },
         }
 
         for as_path, as_path_config in test_data.items():
             path = base_path + ['as-path-list', as_path]
             self.cli_set(path + ['description', f'VyOS-ASPATH-{as_path}'])
             if 'rule' not in as_path_config:
                 continue
 
             for rule, rule_config in as_path_config['rule'].items():
                 if 'action' in rule_config:
                     self.cli_set(path + ['rule', rule, 'action', rule_config['action']])
                 if 'regex' in rule_config:
                     self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']])
 
         self.cli_commit()
 
         config = self.getFRRconfig('bgp as-path access-list', end='')
         for as_path, as_path_config in test_data.items():
             if 'rule' not in as_path_config:
                 continue
 
             for rule, rule_config in as_path_config['rule'].items():
                 tmp = f'bgp as-path access-list {as_path}'
                 if rule_config['action'] == 'permit':
                     tmp += ' permit'
                 else:
                     tmp += ' deny'
 
                 tmp += ' ' + rule_config['regex']
 
                 self.assertIn(tmp, config)
 
     def test_community_list(self):
         test_data = {
             '100' : {
                 'rule' : {
                     '4' : {
                         'action' : 'permit',
                         'regex'  : '.*',
                     },
                 },
             },
             '200' : {
                 'rule' : {
                     '1' : {
                         'action' : 'deny',
                         'regex'  : '^1:201$',
                     },
                     '2' : {
                         'action' : 'deny',
                         'regex'  : '1:101$',
                     },
                     '3' : {
                         'action' : 'deny',
                         'regex'  : '^1:100$',
                     },
                 },
             },
         }
 
         for comm_list, comm_list_config in test_data.items():
             path = base_path + ['community-list', comm_list]
             self.cli_set(path + ['description', f'VyOS-COMM-{comm_list}'])
             if 'rule' not in comm_list_config:
                 continue
 
             for rule, rule_config in comm_list_config['rule'].items():
                 if 'action' in rule_config:
                     self.cli_set(path + ['rule', rule, 'action', rule_config['action']])
                 if 'regex' in rule_config:
                     self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']])
 
         self.cli_commit()
 
         config = self.getFRRconfig('bgp community-list', end='')
         for comm_list, comm_list_config in test_data.items():
             if 'rule' not in comm_list_config:
                 continue
 
             for rule, rule_config in comm_list_config['rule'].items():
                 tmp = f'bgp community-list {comm_list} seq {rule}'
                 if rule_config['action'] == 'permit':
                     tmp += ' permit'
                 else:
                     tmp += ' deny'
 
                 tmp += ' ' + rule_config['regex']
 
                 self.assertIn(tmp, config)
 
     def test_extended_community_list(self):
         test_data = {
             'foo' : {
                 'rule' : {
                     '4' : {
                         'action' : 'permit',
                         'regex'  : '.*',
                     },
                 },
             },
             '200' : {
                 'rule' : {
                     '1' : {
                         'action' : 'deny',
                         'regex'  : '^1:201$',
                     },
                     '2' : {
                         'action' : 'deny',
                         'regex'  : '1:101$',
                     },
                     '3' : {
                         'action' : 'deny',
                         'regex'  : '^1:100$',
                     },
                 },
             },
         }
 
         for comm_list, comm_list_config in test_data.items():
             path = base_path + ['extcommunity-list', comm_list]
             self.cli_set(path + ['description', f'VyOS-EXTCOMM-{comm_list}'])
             if 'rule' not in comm_list_config:
                 continue
 
             for rule, rule_config in comm_list_config['rule'].items():
                 if 'action' in rule_config:
                     self.cli_set(path + ['rule', rule, 'action', rule_config['action']])
                 if 'regex' in rule_config:
                     self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']])
 
         self.cli_commit()
 
         config = self.getFRRconfig('bgp extcommunity-list', end='')
         for comm_list, comm_list_config in test_data.items():
             if 'rule' not in comm_list_config:
                 continue
 
             for rule, rule_config in comm_list_config['rule'].items():
                 # if the community is not a number but a name, the expanded
                 # keyword is used
                 expanded = ''
                 if not comm_list.isnumeric():
                     expanded = ' expanded'
                 tmp = f'bgp extcommunity-list{expanded} {comm_list} seq {rule}'
 
                 if rule_config['action'] == 'permit':
                     tmp += ' permit'
                 else:
                     tmp += ' deny'
 
                 tmp += ' ' + rule_config['regex']
 
                 self.assertIn(tmp, config)
 
 
     def test_large_community_list(self):
         test_data = {
             'foo' : {
                 'rule' : {
                     '10' : {
                         'action' : 'permit',
                         'regex'  : '667:123:100',
                     },
                 },
             },
             'bar' : {
                 'rule' : {
                     '10' : {
                         'action' : 'permit',
                         'regex'  : '65000:120:10',
                     },
                     '20' : {
                         'action' : 'permit',
                         'regex'  : '65000:120:20',
                     },
                     '30' : {
                         'action' : 'permit',
                         'regex'  : '65000:120:30',
                     },
                 },
             },
         }
 
         for comm_list, comm_list_config in test_data.items():
             path = base_path + ['large-community-list', comm_list]
             self.cli_set(path + ['description', f'VyOS-LARGECOMM-{comm_list}'])
             if 'rule' not in comm_list_config:
                 continue
 
             for rule, rule_config in comm_list_config['rule'].items():
                 if 'action' in rule_config:
                     self.cli_set(path + ['rule', rule, 'action', rule_config['action']])
                 if 'regex' in rule_config:
                     self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']])
 
         self.cli_commit()
 
         config = self.getFRRconfig('bgp large-community-list', end='')
         for comm_list, comm_list_config in test_data.items():
             if 'rule' not in comm_list_config:
                 continue
 
             for rule, rule_config in comm_list_config['rule'].items():
                 tmp = f'bgp large-community-list expanded {comm_list} seq {rule}'
 
                 if rule_config['action'] == 'permit':
                     tmp += ' permit'
                 else:
                     tmp += ' deny'
 
                 tmp += ' ' + rule_config['regex']
 
                 self.assertIn(tmp, config)
 
 
     def test_prefix_list(self):
         test_data = {
             'foo' : {
                 'rule' : {
                     '10' : {
                         'action' : 'permit',
                         'prefix'  : '10.0.0.0/8',
                         'ge' : '16',
                         'le' : '24',
                     },
                     '20' : {
                         'action' : 'deny',
                         'prefix'  : '172.16.0.0/12',
                         'ge' : '16',
                     },
                     '30' : {
                         'action' : 'permit',
                         'prefix'  : '192.168.0.0/16',
                     },
                 },
             },
             'bar' : {
                 'rule' : {
                     '10' : {
                         'action' : 'permit',
                         'prefix'  : '10.0.10.0/24',
                         'ge' : '25',
                         'le' : '26',
                     },
                     '20' : {
                         'action' : 'deny',
                         'prefix'  : '10.0.20.0/24',
                         'le' : '25',
                     },
                     '25' : {
                         'action' : 'permit',
                         'prefix'  : '10.0.25.0/24',
                     },
                 },
             },
         }
 
         for prefix_list, prefix_list_config in test_data.items():
             path = base_path + ['prefix-list', prefix_list]
             self.cli_set(path + ['description', f'VyOS-PFX-LIST-{prefix_list}'])
             if 'rule' not in prefix_list_config:
                 continue
 
             for rule, rule_config in prefix_list_config['rule'].items():
                 if 'action' in rule_config:
                     self.cli_set(path + ['rule', rule, 'action', rule_config['action']])
                 if 'prefix' in rule_config:
                     self.cli_set(path + ['rule', rule, 'prefix', rule_config['prefix']])
                 if 'ge' in rule_config:
                     self.cli_set(path + ['rule', rule, 'ge', rule_config['ge']])
                 if 'le' in rule_config:
                     self.cli_set(path + ['rule', rule, 'le', rule_config['le']])
 
         self.cli_commit()
 
         config = self.getFRRconfig('ip prefix-list', end='')
         for prefix_list, prefix_list_config in test_data.items():
             if 'rule' not in prefix_list_config:
                 continue
 
             for rule, rule_config in prefix_list_config['rule'].items():
                 tmp = f'ip prefix-list {prefix_list} seq {rule}'
 
                 if rule_config['action'] == 'permit':
                     tmp += ' permit'
                 else:
                     tmp += ' deny'
 
                 tmp += ' ' + rule_config['prefix']
 
                 if 'ge' in rule_config:
                     tmp += ' ge ' + rule_config['ge']
                 if 'le' in rule_config:
                     tmp += ' le ' + rule_config['le']
 
                 self.assertIn(tmp, config)
 
 
     def test_prefix_list6(self):
         test_data = {
             'foo' : {
                 'rule' : {
                     '10' : {
                         'action' : 'permit',
                         'prefix'  : '2001:db8::/32',
                         'ge' : '40',
                         'le' : '48',
                     },
                     '20' : {
                         'action' : 'deny',
                         'prefix'  : '2001:db8::/32',
                         'ge' : '48',
                     },
                     '30' : {
                         'action' : 'permit',
                         'prefix'  : '2001:db8:1000::/64',
                     },
                 },
             },
             'bar' : {
                 'rule' : {
                     '10' : {
                         'action' : 'permit',
                         'prefix'  : '2001:db8:100::/40',
                         'ge' : '48',
                     },
                     '20' : {
                         'action' : 'permit',
                         'prefix'  : '2001:db8:200::/40',
                         'ge' : '48',
                     },
                     '25' : {
                         'action' : 'deny',
                         'prefix'  : '2001:db8:300::/40',
                         'le' : '64',
                     },
                 },
             },
         }
 
         for prefix_list, prefix_list_config in test_data.items():
             path = base_path + ['prefix-list6', prefix_list]
             self.cli_set(path + ['description', f'VyOS-PFX-LIST-{prefix_list}'])
             if 'rule' not in prefix_list_config:
                 continue
 
             for rule, rule_config in prefix_list_config['rule'].items():
                 if 'action' in rule_config:
                     self.cli_set(path + ['rule', rule, 'action', rule_config['action']])
                 if 'prefix' in rule_config:
                     self.cli_set(path + ['rule', rule, 'prefix', rule_config['prefix']])
                 if 'ge' in rule_config:
                     self.cli_set(path + ['rule', rule, 'ge', rule_config['ge']])
                 if 'le' in rule_config:
                     self.cli_set(path + ['rule', rule, 'le', rule_config['le']])
 
         self.cli_commit()
 
         config = self.getFRRconfig('ipv6 prefix-list', end='')
         for prefix_list, prefix_list_config in test_data.items():
             if 'rule' not in prefix_list_config:
                 continue
 
             for rule, rule_config in prefix_list_config['rule'].items():
                 tmp = f'ipv6 prefix-list {prefix_list} seq {rule}'
 
                 if rule_config['action'] == 'permit':
                     tmp += ' permit'
                 else:
                     tmp += ' deny'
 
                 tmp += ' ' + rule_config['prefix']
 
                 if 'ge' in rule_config:
                     tmp += ' ge ' + rule_config['ge']
                 if 'le' in rule_config:
                     tmp += ' le ' + rule_config['le']
 
                 self.assertIn(tmp, config)
 
 
     # Test set table for some sources
     def test_table_id(self):
         path = base_path + ['local-route']
 
         sources = ['203.0.113.1', '203.0.113.2']
         rule = '50'
         table = '23'
         for src in sources:
             self.cli_set(path + ['rule', rule, 'set', 'table', table])
             self.cli_set(path + ['rule', rule, 'source', src])
 
         self.cli_commit()
 
-        # Check generated configuration
-
-        # Expected values
         original = """
         50:	from 203.0.113.1 lookup 23
         50:	from 203.0.113.2 lookup 23
         """
         tmp = cmd('ip rule show prio 50')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test set table for fwmark
+    def test_fwmark_table_id(self):
+        path = base_path + ['local-route']
+
+        fwmk = '24'
+        rule = '101'
+        table = '154'
+
+        self.cli_set(path + ['rule', rule, 'set', 'table', table])
+        self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+        self.cli_commit()
+
+        original = """
+        101:    from all fwmark 0x18 lookup 154
+        """
+        tmp = cmd('ip rule show prio 101')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test set table for destination
+    def test_destination_table_id(self):
+        path = base_path + ['local-route']
+
+        dst = '203.0.113.1'
+        rule = '102'
+        table = '154'
+
+        self.cli_set(path + ['rule', rule, 'set', 'table', table])
+        self.cli_set(path + ['rule', rule, 'destination', dst])
+
+        self.cli_commit()
+
+        original = """
+        102:    from all to 203.0.113.1 lookup 154
+        """
+        tmp = cmd('ip rule show prio 102')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test set table for sources with fwmark
+    def test_fwmark_sources_table_id(self):
+        path = base_path + ['local-route']
+
+        sources = ['203.0.113.11', '203.0.113.12']
+        fwmk = '23'
+        rule = '100'
+        table = '150'
+        for src in sources:
+            self.cli_set(path + ['rule', rule, 'set', 'table', table])
+            self.cli_set(path + ['rule', rule, 'source', src])
+            self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+        self.cli_commit()
+
+        original = """
+        100:	from 203.0.113.11 fwmark 0x17 lookup 150
+        100:	from 203.0.113.12 fwmark 0x17 lookup 150
+        """
+        tmp = cmd('ip rule show prio 100')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test set table for sources with iif
+    def test_iif_sources_table_id(self):
+        path = base_path + ['local-route']
+
+        sources = ['203.0.113.11', '203.0.113.12']
+        iif = 'lo'
+        rule = '100'
+        table = '150'
+
+        self.cli_set(path + ['rule', rule, 'set', 'table', table])
+        self.cli_set(path + ['rule', rule, 'inbound-interface', iif])
+        for src in sources:
+            self.cli_set(path + ['rule', rule, 'source', src])
+
+        self.cli_commit()
+
+        # Check generated configuration
+        # Expected values
+        original = """
+        100:	from 203.0.113.11 iif lo lookup 150
+        100:	from 203.0.113.12 iif lo lookup 150
+        """
+        tmp = cmd('ip rule show prio 100')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test set table for sources and destinations with fwmark
+    def test_fwmark_sources_destination_table_id(self):
+        path = base_path + ['local-route']
+
+        sources = ['203.0.113.11', '203.0.113.12']
+        destinations = ['203.0.113.13', '203.0.113.15']
+        fwmk = '23'
+        rule = '103'
+        table = '150'
+        for src in sources:
+            for dst in destinations:
+                self.cli_set(path + ['rule', rule, 'set', 'table', table])
+                self.cli_set(path + ['rule', rule, 'source', src])
+                self.cli_set(path + ['rule', rule, 'destination', dst])
+                self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+        self.cli_commit()
+
+        original = """
+        103:	from 203.0.113.11 to 203.0.113.13 fwmark 0x17 lookup 150
+        103:	from 203.0.113.11 to 203.0.113.15 fwmark 0x17 lookup 150
+        103:	from 203.0.113.12 to 203.0.113.13 fwmark 0x17 lookup 150
+        103:	from 203.0.113.12 to 203.0.113.15 fwmark 0x17 lookup 150
+        """
+        tmp = cmd('ip rule show prio 103')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test set table ipv6 for some sources ipv6
+    def test_ipv6_table_id(self):
+        path = base_path + ['local-route6']
+
+        sources = ['2001:db8:123::/48', '2001:db8:126::/48']
+        rule = '50'
+        table = '23'
+        for src in sources:
+            self.cli_set(path + ['rule', rule, 'set', 'table', table])
+            self.cli_set(path + ['rule', rule, 'source', src])
+
+        self.cli_commit()
+
+        original = """
+        50:	from 2001:db8:123::/48 lookup 23
+        50:	from 2001:db8:126::/48 lookup 23
+        """
+        tmp = cmd('ip -6 rule show prio 50')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test set table for fwmark ipv6
+    def test_fwmark_ipv6_table_id(self):
+        path = base_path + ['local-route6']
+
+        fwmk = '24'
+        rule = '100'
+        table = '154'
+
+        self.cli_set(path + ['rule', rule, 'set', 'table', table])
+        self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+        self.cli_commit()
+
+        original = """
+        100:    from all fwmark 0x18 lookup 154
+        """
+        tmp = cmd('ip -6 rule show prio 100')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test set table for destination ipv6
+    def test_destination_ipv6_table_id(self):
+        path = base_path + ['local-route6']
+
+        dst = '2001:db8:1337::/126'
+        rule = '101'
+        table = '154'
+
+        self.cli_set(path + ['rule', rule, 'set', 'table', table])
+        self.cli_set(path + ['rule', rule, 'destination', dst])
+
+        self.cli_commit()
+
+        original = """
+        101:    from all to 2001:db8:1337::/126 lookup 154
+        """
+        tmp = cmd('ip -6 rule show prio 101')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test set table for sources with fwmark ipv6
+    def test_fwmark_sources_ipv6_table_id(self):
+        path = base_path + ['local-route6']
+
+        sources = ['2001:db8:1338::/126', '2001:db8:1339::/126']
+        fwmk = '23'
+        rule = '102'
+        table = '150'
+        for src in sources:
+            self.cli_set(path + ['rule', rule, 'set', 'table', table])
+            self.cli_set(path + ['rule', rule, 'source', src])
+            self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+        self.cli_commit()
+
+        original = """
+        102:	from 2001:db8:1338::/126 fwmark 0x17 lookup 150
+        102:	from 2001:db8:1339::/126 fwmark 0x17 lookup 150
+        """
+        tmp = cmd('ip -6 rule show prio 102')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test set table for sources with iif ipv6
+    def test_iif_sources_ipv6_table_id(self):
+        path = base_path + ['local-route6']
+
+        sources = ['2001:db8:1338::/126', '2001:db8:1339::/126']
+        iif = 'lo'
+        rule = '102'
+        table = '150'
+        for src in sources:
+            self.cli_set(path + ['rule', rule, 'set', 'table', table])
+            self.cli_set(path + ['rule', rule, 'source', src])
+            self.cli_set(path + ['rule', rule, 'inbound-interface', iif])
+
+        self.cli_commit()
+
+        # Check generated configuration
+        # Expected values
+        original = """
+        102:	from 2001:db8:1338::/126 iif lo lookup 150
+        102:	from 2001:db8:1339::/126 iif lo lookup 150
+        """
+        tmp = cmd('ip -6 rule show prio 102')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test set table for sources and destinations with fwmark ipv6
+    def test_fwmark_sources_destination_ipv6_table_id(self):
+        path = base_path + ['local-route6']
+
+        sources = ['2001:db8:1338::/126', '2001:db8:1339::/56']
+        destinations = ['2001:db8:13::/48', '2001:db8:16::/48']
+        fwmk = '23'
+        rule = '103'
+        table = '150'
+        for src in sources:
+            for dst in destinations:
+                self.cli_set(path + ['rule', rule, 'set', 'table', table])
+                self.cli_set(path + ['rule', rule, 'source', src])
+                self.cli_set(path + ['rule', rule, 'destination', dst])
+                self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+        self.cli_commit()
+
+        original = """
+        103:	from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+        103:	from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+        103:	from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+        103:	from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+        """
+        tmp = cmd('ip -6 rule show prio 103')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+    # Test delete table for sources and destination with fwmark ipv4/ipv6
+    def test_delete_ipv4_ipv6_table_id(self):
+        path = base_path + ['local-route']
+        path_v6 = base_path + ['local-route6']
+
+        sources = ['203.0.113.0/24', '203.0.114.5']
+        destinations = ['203.0.112.0/24', '203.0.116.5']
+        sources_v6 = ['2001:db8:1338::/126', '2001:db8:1339::/56']
+        destinations_v6 = ['2001:db8:13::/48', '2001:db8:16::/48']
+        fwmk = '23'
+        rule = '103'
+        table = '150'
+        for src in sources:
+            for dst in destinations:
+                self.cli_set(path + ['rule', rule, 'set', 'table', table])
+                self.cli_set(path + ['rule', rule, 'source', src])
+                self.cli_set(path + ['rule', rule, 'destination', dst])
+                self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+        for src in sources_v6:
+            for dst in destinations_v6:
+                self.cli_set(path_v6 + ['rule', rule, 'set', 'table', table])
+                self.cli_set(path_v6 + ['rule', rule, 'source', src])
+                self.cli_set(path_v6 + ['rule', rule, 'destination', dst])
+                self.cli_set(path_v6 + ['rule', rule, 'fwmark', fwmk])
+
+        self.cli_commit()
+
+        original = """
+        103:	from 203.0.113.0/24 to 203.0.116.5 fwmark 0x17 lookup 150
+        103:	from 203.0.114.5 to 203.0.112.0/24 fwmark 0x17 lookup 150
+        103:	from 203.0.114.5 to 203.0.116.5 fwmark 0x17 lookup 150
+        103:	from 203.0.113.0/24 to 203.0.112.0/24 fwmark 0x17 lookup 150
+        """
+        original_v6 = """
+        103:	from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+        103:	from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+        103:	from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+        103:	from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+        """
+        tmp = cmd('ip rule show prio 103')
+        tmp_v6 = cmd('ip -6 rule show prio 103')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original))
+        self.assertEqual(sort_ip(tmp_v6), sort_ip(original_v6))
+
+        self.cli_delete(path)
+        self.cli_delete(path_v6)
+        self.cli_commit()
+
+        tmp = cmd('ip rule show prio 103')
+        tmp_v6 = cmd('ip -6 rule show prio 103')
+
+        self.assertEqual(sort_ip(tmp), [])
+        self.assertEqual(sort_ip(tmp_v6), [])
+
+    # Test multiple commits ipv4
+    def test_multiple_commit_ipv4_table_id(self):
+        path = base_path + ['local-route']
+
+        sources = ['192.0.2.1', '192.0.2.2']
+        destination = '203.0.113.25'
+        rule = '105'
+        table = '151'
+        self.cli_set(path + ['rule', rule, 'set', 'table', table])
+        for src in sources:
+            self.cli_set(path + ['rule', rule, 'source', src])
+
+        self.cli_commit()
+
+        original_first = """
+        105:	from 192.0.2.1 lookup 151
+        105:	from 192.0.2.2 lookup 151
+        """
+        tmp = cmd('ip rule show prio 105')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original_first))
+
+        # Create second commit with added destination
+        self.cli_set(path + ['rule', rule, 'destination', destination])
+        self.cli_commit()
+
+        original_second = """
+        105:	from 192.0.2.1 to 203.0.113.25 lookup 151
+        105:	from 192.0.2.2 to 203.0.113.25 lookup 151
+        """
+        tmp = cmd('ip rule show prio 105')
+
+        self.assertEqual(sort_ip(tmp), sort_ip(original_second))
+
+
+def sort_ip(output):
+    o = '\n'.join([' '.join(line.strip().split()) for line in output.strip().splitlines()])
+    o = o.splitlines()
+    o.sort()
+    return o
+
+    # Test set table for fwmark
+    def test_fwmark_table_id(self):
+        path = base_path + ['local-route']
+
+        fwmk = '24'
+        rule = '101'
+        table = '154'
+
+        self.cli_set(path + ['rule', rule, 'set', 'table', table])
+        self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+        self.cli_commit()
+
+        # Check generated configuration
+
+        # Expected values
+        original = """
+        101:    from all fwmark 0x18 lookup 154
+        """
+        tmp = cmd('ip rule show prio 101')
+        original = original.split()
+        tmp = tmp.split()
+
+        self.assertEqual(tmp, original)
+
+    # Test set table for sources with fwmark
+    def test_fwmark_sources_table_id(self):
+        path = base_path + ['local-route']
+
+        sources = ['203.0.113.11', '203.0.113.12']
+        fwmk = '23'
+        rule = '100'
+        table = '150'
+        for src in sources:
+            self.cli_set(path + ['rule', rule, 'set', 'table', table])
+            self.cli_set(path + ['rule', rule, 'source', src])
+            self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+        self.cli_commit()
+
+        # Check generated configuration
+
+        # Expected values
+        original = """
+        100:	from 203.0.113.11 fwmark 0x17 lookup 150
+        100:	from 203.0.113.12 fwmark 0x17 lookup 150
+        """
+        tmp = cmd('ip rule show prio 100')
         original = original.split()
         tmp = tmp.split()
 
         self.assertEqual(tmp, original)
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py
index 013f22665..0a4597869 100755
--- a/src/conf_mode/policy-local-route.py
+++ b/src/conf_mode/policy-local-route.py
@@ -1,110 +1,225 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2021 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 from sys import exit
 
+from netifaces import interfaces
 from vyos.config import Config
 from vyos.configdict import dict_merge
 from vyos.configdict import node_changed
 from vyos.configdict import leaf_node_changed
 from vyos.template import render
 from vyos.util import call
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 
 def get_config(config=None):
 
     if config:
         conf = config
     else:
         conf = Config()
-    base = ['policy', 'local-route']
+    base = ['policy']
+
     pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
 
-    # delete policy local-route
-    dict = {}
-    tmp = node_changed(conf, ['policy', 'local-route', 'rule'], key_mangling=('-', '_'))
-    if tmp:
-        for rule in (tmp or []):
-            src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source'])
-            if src:
-                dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict)
+    for route in ['local_route', 'local_route6']:
+        dict_id = 'rule_remove' if route == 'local_route' else 'rule6_remove'
+        route_key = 'local-route' if route == 'local_route' else 'local-route6'
+        base_rule = base + [route_key, 'rule']
+
+        # delete policy local-route
+        dict = {}
+        tmp = node_changed(conf, base_rule, key_mangling=('-', '_'))
+        if tmp:
+            for rule in (tmp or []):
+                src = leaf_node_changed(conf, base_rule + [rule, 'source'])
+                fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
+                iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
+                dst = leaf_node_changed(conf, base_rule + [rule, 'destination'])
+                rule_def = {}
+                if src:
+                    rule_def = dict_merge({'source' : src}, rule_def)
+                if fwmk:
+                    rule_def = dict_merge({'fwmark' : fwmk}, rule_def)
+                if iif:
+                    rule_def = dict_merge({'inbound_interface' : iif}, rule_def)
+                if dst:
+                    rule_def = dict_merge({'destination' : dst}, rule_def)
+                dict = dict_merge({dict_id : {rule : rule_def}}, dict)
                 pbr.update(dict)
-
-    # delete policy local-route rule x source x.x.x.x
-    if 'rule' in pbr:
-        for rule in pbr['rule']:
-            src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source'])
-            if src:
-                dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict)
+            if fwmk:
+                dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict)
                 pbr.update(dict)
 
+        if not route in pbr:
+            continue
+
+        # delete policy local-route rule x source x.x.x.x
+        # delete policy local-route rule x fwmark x
+        # delete policy local-route rule x destination x.x.x.x
+        if 'rule' in pbr[route]:
+            for rule, rule_config in pbr[route]['rule'].items():
+                src = leaf_node_changed(conf, base_rule + [rule, 'source'])
+                fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
+                iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
+                dst = leaf_node_changed(conf, base_rule + [rule, 'destination'])
+                # keep track of changes in configuration
+                # otherwise we might remove an existing node although nothing else has changed
+                changed = False
+
+                rule_def = {}
+                # src is None if there are no changes to src
+                if src is None:
+                    # if src hasn't changed, include it in the removal selector
+                    # if a new selector is added, we have to remove all previous rules without this selector
+                    # to make sure we remove all previous rules with this source(s), it will be included
+                    if 'source' in rule_config:
+                        rule_def = dict_merge({'source': rule_config['source']}, rule_def)
+                else:
+                    # if src is not None, it's previous content will be returned
+                    # this can be an empty array if it's just being set, or the previous value
+                    # either way, something has to be changed and we only want to remove previous values
+                    changed = True
+                    # set the old value for removal if it's not empty
+                    if len(src) > 0:
+                        rule_def = dict_merge({'source' : src}, rule_def)
+                if fwmk is None:
+                    if 'fwmark' in rule_config:
+                        rule_def = dict_merge({'fwmark': rule_config['fwmark']}, rule_def)
+                else:
+                    changed = True
+                    if len(fwmk) > 0:
+                        rule_def = dict_merge({'fwmark' : fwmk}, rule_def)
+                if iif is None:
+                    if 'inbound_interface' in rule_config:
+                        rule_def = dict_merge({'inbound_interface': rule_config['inbound_interface']}, rule_def)
+                else:
+                    changed = True
+                    if len(iif) > 0:
+                        rule_def = dict_merge({'inbound_interface' : iif}, rule_def)
+                if dst is None:
+                    if 'destination' in rule_config:
+                        rule_def = dict_merge({'destination': rule_config['destination']}, rule_def)
+                else:
+                    changed = True
+                    if len(dst) > 0:
+                        rule_def = dict_merge({'destination' : dst}, rule_def)
+                if changed:
+                    dict = dict_merge({dict_id : {rule : rule_def}}, dict)
+                    pbr.update(dict)
+
     return pbr
 
 def verify(pbr):
     # bail out early - looks like removal from running config
     if not pbr:
         return None
 
-    if 'rule' in pbr:
-        for rule in pbr['rule']:
-            if 'source' not in pbr['rule'][rule]:
-                raise ConfigError('Source address required!')
-            else:
-                if 'set' not in pbr['rule'][rule] or 'table' not in pbr['rule'][rule]['set']:
-                    raise ConfigError('Table set is required!')
+    for route in ['local_route', 'local_route6']:
+        if not route in pbr:
+            continue
+
+        pbr_route = pbr[route]
+        if 'rule' in pbr_route:
+            for rule in pbr_route['rule']:
+                if 'source' not in pbr_route['rule'][rule] \
+                        and 'destination' not in pbr_route['rule'][rule] \
+                        and 'fwmark' not in pbr_route['rule'][rule] \
+                        and 'inbound_interface' not in pbr_route['rule'][rule]:
+                    raise ConfigError('Source or destination address or fwmark or inbound-interface is required!')
+                else:
+                    if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']:
+                        raise ConfigError('Table set is required!')
+                    if 'inbound_interface' in pbr_route['rule'][rule]:
+                        interface = pbr_route['rule'][rule]['inbound_interface']
+                        if interface not in interfaces():
+                            raise ConfigError(f'Interface "{interface}" does not exist')
 
     return None
 
 def generate(pbr):
     if not pbr:
         return None
 
     return None
 
 def apply(pbr):
     if not pbr:
         return None
 
     # Delete old rule if needed
-    if 'rule_remove' in pbr:
-        for rule in pbr['rule_remove']:
-            for src in pbr['rule_remove'][rule]['source']:
-                call(f'ip rule del prio {rule} from {src}')
+    for rule_rm in ['rule_remove', 'rule6_remove']:
+        if rule_rm in pbr:
+            v6 = " -6" if rule_rm == 'rule6_remove' else ""
+            for rule, rule_config in pbr[rule_rm].items():
+                rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['']
+                for src in rule_config['source']:
+                    f_src = '' if src == '' else f' from {src} '
+                    rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['']
+                    for dst in rule_config['destination']:
+                        f_dst = '' if dst == '' else f' to {dst} '
+                        rule_config['fwmark'] = rule_config['fwmark'] if 'fwmark' in rule_config else ['']
+                        for fwmk in rule_config['fwmark']:
+                            f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} '
+                            rule_config['inbound_interface'] = rule_config['inbound_interface'] if 'inbound_interface' in rule_config else ['']
+                            for iif in rule_config['inbound_interface']:
+                                f_iif = '' if iif == '' else f' iif {iif} '
+                                call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}')
 
     # Generate new config
-    if 'rule' in pbr:
-        for rule in pbr['rule']:
-            table = pbr['rule'][rule]['set']['table']
-            if pbr['rule'][rule]['source']:
-                for src in pbr['rule'][rule]['source']:
-                    call(f'ip rule add prio {rule} from {src} lookup {table}')
+    for route in ['local_route', 'local_route6']:
+        if not route in pbr:
+            continue
+
+        v6 = " -6" if route == 'local_route6' else ""
+
+        pbr_route = pbr[route]
+        if 'rule' in pbr_route:
+            for rule, rule_config in pbr_route['rule'].items():
+                table = rule_config['set']['table']
+
+                rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['all']
+                for src in rule_config['source'] or ['all']:
+                    f_src = '' if src == '' else f' from {src} '
+                    rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['all']
+                    for dst in rule_config['destination']:
+                        f_dst = '' if dst == '' else f' to {dst} '
+                        f_fwmk = ''
+                        if 'fwmark' in rule_config:
+                            fwmk = rule_config['fwmark']
+                            f_fwmk = f' fwmark {fwmk} '
+                        f_iif = ''
+                        if 'inbound_interface' in rule_config:
+                            iif = rule_config['inbound_interface']
+                            f_iif = f' iif {iif} '
+                        call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif} lookup {table}')
 
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)