diff --git a/ansible/filter_plugins/networks.py b/ansible/filter_plugins/networks.py
index 27f8787c9..e48ee432e 100644
--- a/ansible/filter_plugins/networks.py
+++ b/ansible/filter_plugins/networks.py
@@ -125,6 +125,7 @@ def net_vlan(context, name, inventory_hostname=None):
 
 net_mtu = _make_attr_filter('mtu')
 net_routes = _make_attr_filter('routes')
+net_rules = _make_attr_filter('rules')
 net_physical_network = _make_attr_filter('physical_network')
 
 
@@ -154,14 +155,21 @@ def _route_obj(route):
 
     The returned dict is compatible with the route item of the
     interfaces_ether_interfaces and interfaces_bridge_interfaces variables in
-    the MichaelRigaert.interfaces role.
+    the MichaelRigart.interfaces role.
     """
     net = netaddr.IPNetwork(route['cidr'])
-    return {
+    route_obj = {
         'network': str(net.network),
         'netmask': str(net.netmask),
-        'gateway': route['gateway'],
     }
+    optional = {
+        'gateway',
+        'table',
+    }
+    for option in optional:
+        if option in route:
+            route_obj[option] = route[option]
+    return route_obj
 
 
 @jinja2.contextfilter
@@ -187,6 +195,7 @@ def net_interface_obj(context, name, inventory_hostname=None):
     routes = net_routes(context, name, inventory_hostname)
     if routes:
         routes = [_route_obj(route) for route in routes]
+    rules = net_rules(context, name, inventory_hostname)
     interface = {
         'device': device,
         'address': ip,
@@ -195,6 +204,7 @@ def net_interface_obj(context, name, inventory_hostname=None):
         'vlan': vlan,
         'mtu': mtu,
         'route': routes,
+        'rules': rules,
         'bootproto': 'static',
         'onboot': 'yes',
     }
@@ -226,6 +236,7 @@ def net_bridge_obj(context, name, inventory_hostname=None):
     routes = net_routes(context, name, inventory_hostname)
     if routes:
         routes = [_route_obj(route) for route in routes]
+    rules = net_rules(context, name, inventory_hostname)
     interface = {
         'device': device,
         'address': ip,
@@ -235,6 +246,7 @@ def net_bridge_obj(context, name, inventory_hostname=None):
         'mtu': mtu,
         'ports': ports,
         'route': routes,
+        'rules': rules,
         'bootproto': 'static',
         'onboot': 'yes',
     }
@@ -268,6 +280,7 @@ def net_bond_obj(context, name, inventory_hostname=None):
     routes = net_routes(context, name, inventory_hostname)
     if routes:
         routes = [_route_obj(route) for route in routes]
+    rules = net_rules(context, name, inventory_hostname)
     interface = {
         'device': device,
         'address': ip,
@@ -279,6 +292,7 @@ def net_bond_obj(context, name, inventory_hostname=None):
         'bond_mode': mode,
         'bond_miimon': miimon,
         'route': routes,
+        'rules': rules,
         'bootproto': 'static',
         'onboot': 'yes',
     }
@@ -429,6 +443,7 @@ class FilterModule(object):
             'net_vlan': net_vlan,
             'net_mtu': net_mtu,
             'net_routes': net_routes,
+            'net_rules': net_rules,
             'net_physical_network': net_physical_network,
             'net_interface_obj': net_interface_obj,
             'net_bridge_obj': net_bridge_obj,
diff --git a/ansible/group_vars/all/network b/ansible/group_vars/all/network
index c90bf29e4..1d9caa53a 100644
--- a/ansible/group_vars/all/network
+++ b/ansible/group_vars/all/network
@@ -54,3 +54,10 @@ network_patch_suffix_phy: '-phy'
 # Suffix for virtual patch link interface names when connected towards the
 # OVS bridge.
 network_patch_suffix_ovs: '-ovs'
+
+###############################################################################
+# Network routing table configuration.
+
+# List of IP routing tables. Each item should be a dict containing 'id' and
+# 'name' items. These tables will be added to /etc/iproute2/rt_tables.
+network_route_tables: []
diff --git a/ansible/network.yml b/ansible/network.yml
index 146045103..e5115f55e 100644
--- a/ansible/network.yml
+++ b/ansible/network.yml
@@ -52,6 +52,7 @@
       become: True
 
     - role: MichaelRigart.interfaces
+      interfaces_route_tables: "{{ network_route_tables }}"
       interfaces_ether_interfaces: >
         {{ ether_interfaces |
            map('net_interface_obj') |
diff --git a/doc/source/configuration/network.rst b/doc/source/configuration/network.rst
index 604c7bf5e..1f7c490c9 100644
--- a/doc/source/configuration/network.rst
+++ b/doc/source/configuration/network.rst
@@ -41,8 +41,13 @@ supported:
     Maximum Transmission Unit (MTU).
 ``routes``
     List of static IP routes. Each item should be a dict containing the
-    items ``cidr`` and ``gateway``. ``cidr`` is the CIDR representation of the
-    route's destination. ``gateway`` is the IP address of the next hop.
+    item ``cidr``, and optionally ``gateway`` and ``table``. ``cidr`` is the CIDR
+    representation of the route's destination. ``gateway`` is the IP address of
+    the next hop. ``table`` is the name or ID of a routing table to which the
+    route will be added.
+``rules``
+    List of IP routing rules. Each item should be an ``iproute2`` IP routing
+    rule.
 ``physical_network``
     Name of the physical network on which this network exists. This aligns with
     the physical network concept in neutron.
@@ -86,7 +91,8 @@ Configuring Static IP Routes
 Static IP routes may be configured by setting the ``routes`` attribute for a
 network to a list of routes.
 
-To configure a network called ``example`` with a single IP route:
+To configure a network called ``example`` with a single IP route to the
+``10.1.0.0/24`` subnet via ``10.0.0.1``:
 
 .. code-block:: yaml
    :caption: ``networks.yml``
@@ -155,6 +161,71 @@ addresses for hosts ``host1`` and ``host2``:
      host1: 10.0.0.1
      host2: 10.0.0.2
 
+Advanced: Policy-Based Routing
+------------------------------
+
+Policy-based routing can be useful in complex networking environments,
+particularly where asymmetric routes exist, and strict reverse path filtering
+is enabled.
+
+Configuring IP Routing Tables
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Custom IP routing tables may be configured by setting the global variable
+``network_route_tables`` in ``${KAYOBE_CONFIG_PATH}/networks.yml`` to a list of
+route tables. These route tables will be added to ``/etc/iproute2/rt_tables``.
+
+To configure a routing table called ``exampleroutetable`` with ID ``1``:
+
+.. code-block:: yaml
+   :caption: ``networks.yml``
+
+   network_route_tables:
+     - name: exampleroutetable
+       id: 1
+
+To configure route tables on specific hosts, use a host or group variables
+file.
+
+Configuring IP Routing Policy Rules
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+IP routing policy rules may be configured by setting the ``rules`` attribute
+for a network to a list of rules. The format of a rule is the string which
+would be appended to ``ip rule <add|del>`` to create or delete the rule.
+
+To configure a network called ``example`` with an IP routing policy rule to
+handle traffic from the subnet ``10.1.0.0/24`` using the routing table
+``exampleroutetable``:
+
+.. code-block:: yaml
+   :caption: ``networks.yml``
+
+   example_rules:
+     - from 10.1.0.0/24 table exampleroutetable
+
+These rules will be configured on all hosts to which the network is mapped.
+
+Configuring IP Routes on Specific Tables
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A route may be added to a specific routing table by adding the name or ID of
+the table to a ``table`` attribute of the route:
+
+To configure a network called ``example`` with a default route and a
+'connected' (local subnet) route to the subnet ``10.1.0.0/24`` on the table
+``exampleroutetable``:
+
+.. code-block:: yaml
+   :caption: ``networks.yml``
+
+   example_routes:
+     - cidr: 0.0.0.0/0
+       gateway 10.1.0.1
+       table: exampleroutetable
+     - cidr: 10.1.0.0/24
+       table: exampleroutetable
+
 Per-host Network Configuration
 ==============================
 
diff --git a/etc/kayobe/networks.yml b/etc/kayobe/networks.yml
index 0d43d5ec0..cc5ec3bb9 100644
--- a/etc/kayobe/networks.yml
+++ b/etc/kayobe/networks.yml
@@ -150,6 +150,13 @@
 # OVS bridge.
 #network_patch_suffix_ovs:
 
+###############################################################################
+# Network routing table configuration.
+
+# List of IP routing tables. Each item should be a dict containing 'id' and
+# 'name' items. These tables will be added to /etc/iproute2/rt_tables.
+#network_route_tables:
+
 ###############################################################################
 # Dummy variable to allow Ansible to accept this file.
 workaround_ansible_issue_8743: yes