diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | data/configd-include.json | 1 | ||||
-rw-r--r-- | data/templates/frr/policy.frr.tmpl | 282 | ||||
-rw-r--r-- | interface-definitions/include/policy-list-description.xml.i | 11 | ||||
-rw-r--r-- | interface-definitions/include/policy-list-rule-description.xml.i | 11 | ||||
-rw-r--r-- | interface-definitions/include/policy/action.xml.i (renamed from interface-definitions/include/policy-list-action.xml.i) | 0 | ||||
-rw-r--r-- | interface-definitions/include/policy/description.xml.i | 11 | ||||
-rw-r--r-- | interface-definitions/include/policy/host.xml.i | 14 | ||||
-rw-r--r-- | interface-definitions/include/policy/inverse-mask.xml.i | 14 | ||||
-rw-r--r-- | interface-definitions/include/policy/network.xml.i | 14 | ||||
-rw-r--r-- | interface-definitions/policy.xml.in | 162 | ||||
-rw-r--r-- | python/vyos/template.py | 15 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_policy.py | 9 | ||||
-rwxr-xr-x | src/conf_mode/policy.py | 98 |
14 files changed, 451 insertions, 193 deletions
@@ -39,12 +39,10 @@ interface_definitions: $(config_xml_obj) # XXX: delete top level node.def's that now live in other packages rm -f $(TMPL_DIR)/firewall/node.def - rm -f $(TMPL_DIR)/policy/node.def rm -f $(TMPL_DIR)/system/node.def rm -f $(TMPL_DIR)/vpn/node.def rm -f $(TMPL_DIR)/vpn/ipsec/node.def rm -rf $(TMPL_DIR)/vpn/nipsec - rm -rf $(TMPL_DIR)/npolicy # XXX: test if there are empty node.def files - this is not allowed as these # could mask help strings or mandatory priority statements diff --git a/data/configd-include.json b/data/configd-include.json index 4959e5020..f241d0cb6 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -30,6 +30,7 @@ "nat.py", "nat66.py", "ntp.py", +"policy.py", "policy-local-route.py", "protocols_bfd.py", "protocols_bgp.py", diff --git a/data/templates/frr/policy.frr.tmpl b/data/templates/frr/policy.frr.tmpl new file mode 100644 index 000000000..2d02efa52 --- /dev/null +++ b/data/templates/frr/policy.frr.tmpl @@ -0,0 +1,282 @@ +!
+{% if policy.access_list is defined and policy.access_list is not none %}
+{% for acl, acl_config in policy.access_list.items() | natural_sort %}
+{% if acl_config.description is defined and acl_config.description is not none %}
+access-list {{ acl }} remark {{ acl_config.description }}
+{% endif %}
+{% if acl_config.rule is defined and acl_config.rule is not none %}
+{% for rule, rule_config in acl_config.rule.items() | natural_sort %}
+{% set ip = '' %}
+{% set src = '' %}
+{% set src_mask = '' %}
+{% if rule_config.source is defined and rule_config.source.any is defined %}
+{% set src = 'any' %}
+{% elif rule_config.source is defined and rule_config.source.host is defined and rule_config.source.host is not none %}
+{% set src = 'host ' + rule_config.source.host %}
+{% elif rule_config.source is defined and rule_config.source.network is defined and rule_config.source.network is not none %}
+{% set src = rule_config.source.network %}
+{% set src_mask = rule_config.source.inverse_mask %}
+{% endif %}
+{% set dst = '' %}
+{% set dst_mask = '' %}
+{% if (acl|int >= 100 and acl|int <= 199) or (acl|int >= 2000 and acl|int <= 2699) %}
+{% set ip = 'ip' %}
+{% set dst = 'any' %}
+{% if rule_config.destination is defined and rule_config.destination.any is defined %}
+{% set dst = 'any' %}
+{% elif rule_config.destination is defined and rule_config.destination.host is defined and rule_config.destination.host is not none %}
+{% set dst = 'host ' + rule_config.destination.host %}
+{% elif rule_config.destination is defined and rule_config.destination.network is defined and rule_config.destination.network is not none %}
+{% set dst = rule_config.destination.network %}
+{% set dst_mask = rule_config.destination.inverse_mask %}
+{% endif %}
+{% endif %}
+access-list {{ acl }} seq {{ rule }} {{ rule_config.action }} {{ ip }} {{ src }} {{ src_mask }} {{ dst }} {{ dst_mask }}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
+!
+{% if policy.access_list6 is defined and policy.access_list6 is not none %}
+{% for acl, acl_config in policy.access_list6.items() | natural_sort %}
+{% if acl_config.description is defined and acl_config.description is not none %}
+ipv6 access-list {{ acl }} remark {{ acl_config.description }}
+{% endif %}
+{% if acl_config.rule is defined and acl_config.rule is not none %}
+{% for rule, rule_config in acl_config.rule.items() | natural_sort %}
+{% set src = '' %}
+{% if rule_config.source is defined and rule_config.source.any is defined %}
+{% set src = 'any' %}
+{% elif rule_config.source is defined and rule_config.source.network is defined and rule_config.source.network is not none %}
+{% set src = rule_config.source.network %}
+{% endif %}
+ipv6 access-list {{ acl }} seq {{ rule }} {{ rule_config.action }} {{ src }} {{ 'exact-match' if rule_config.source.exact_match is defined }}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
+!
+{% if policy.as_path_list is defined and policy.as_path_list is not none %}
+{% for acl, acl_config in policy.as_path_list.items() | natural_sort %}
+{% if acl_config.rule is defined and acl_config.rule is not none %}
+{% for rule, rule_config in acl_config.rule.items() | natural_sort %}
+bgp as-path access-list {{ acl }} {{ rule_config.action }} {{ rule_config.regex }}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
+!
+{% if policy.community_list is defined and policy.community_list is not none %}
+{% for list, list_config in policy.community_list.items() | natural_sort %}
+{% if list_config.rule is defined and list_config.rule is not none %}
+{% for rule, rule_config in list_config.rule.items() | natural_sort %}
+{# by default, if casting to int fails it returns 0 #}
+{% if list|int != 0 %}
+bgp community-list {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{% else %}
+bgp community-list expanded {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
+!
+{% if policy.extcommunity_list is defined and policy.extcommunity_list is not none %}
+{% for list, list_config in policy.extcommunity_list.items() | natural_sort %}
+{% if list_config.rule is defined and list_config.rule is not none %}
+{% for rule, rule_config in list_config.rule.items() | natural_sort %}
+{# by default, if casting to int fails it returns 0 #}
+{% if list|int != 0 %}
+bgp extcommunity-list {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{% else %}
+bgp extcommunity-list expanded {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
+!
+{% if policy.large_community_list is defined and policy.large_community_list is not none %}
+{% for list, list_config in policy.large_community_list.items() | natural_sort %}
+{% if list_config.rule is defined and list_config.rule is not none %}
+{% for rule, rule_config in list_config.rule.items() | natural_sort %}
+{# by default, if casting to int fails it returns 0 #}
+{% if list|int != 0 %}
+bgp large-community-list {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{% else %}
+bgp large-community-list expanded {{ list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
+!
+{% if policy.prefix_list is defined and policy.prefix_list is not none %}
+{% for prefix_list, prefix_list_config in policy.prefix_list.items() | natural_sort %}
+{% if prefix_list_config.description is defined and prefix_list_config.description is not none %}
+ip prefix-list {{ prefix_list }} description {{ prefix_list_config.description }}
+{% endif %}
+{% if prefix_list_config.rule is defined and prefix_list_config.rule is not none %}
+{% for rule, rule_config in prefix_list_config.rule.items() | natural_sort %}
+ip prefix-list {{ prefix_list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.prefix }} {{ 'ge ' + rule_config.ge if rule_config.ge is defined }} {{ 'le ' + rule_config.le if rule_config.le is defined }}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
+!
+{% if policy.prefix_list6 is defined and policy.prefix_list6 is not none %}
+{% for prefix_list, prefix_list_config in policy.prefix_list6.items() | natural_sort %}
+{% if prefix_list_config.description is defined and prefix_list_config.description is not none %}
+ipv6 prefix-list {{ prefix_list }} description {{ prefix_list_config.description }}
+{% endif %}
+{% if prefix_list_config.rule is defined and prefix_list_config.rule is not none %}
+{% for rule, rule_config in prefix_list_config.rule.items() | natural_sort %}
+ipv6 prefix-list {{ prefix_list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.prefix }} {{ 'ge ' + rule_config.ge if rule_config.ge is defined }} {{ 'le ' + rule_config.le if rule_config.le is defined }}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
+!
+{% if policy.route_map is defined and policy.route_map is not none %}
+{% for route_map, route_map_config in policy.route_map.items() | natural_sort %}
+{% if route_map_config.rule is defined and route_map_config.rule is not none %}
+{% for rule, rule_config in route_map_config.rule.items() | natural_sort %}
+route-map {{ route_map }} {{ rule_config.action }} {{ rule }}
+{% if rule_config.call is defined and rule_config.call is not none %}
+ call {{ rule_config.call }}
+{% endif %}
+{% if rule_config.continue is defined and rule_config.continue is not none %}
+ on-match goto {{ rule_config.continue }}
+{% endif %}
+{% if rule_config.description is defined and rule_config.description is not none %}
+ description {{ rule_config.description }}
+{% endif %}
+{% if rule_config.match is defined and rule_config.match is not none %}
+{% if rule_config.match.as_path is defined and rule_config.match.as_path is not none %}
+ match as-path {{ rule_config.match.as_path }}
+{% endif %}
+{% if rule_config.match.community is defined and rule_config.match.community.community_list is defined and rule_config.match.community.community_list is not none %}
+ match community {{ rule_config.match.community.community_list }} {{ 'exact-match' if rule_config.match.community.exact_match is defined }}
+{% endif %}
+{% if rule_config.match.extcommunity is defined and rule_config.match.extcommunity is not none %}
+ match extcommunity {{ rule_config.match.extcommunity }}
+{% endif %}
+{% if rule_config.match.interface is defined and rule_config.match.interface is not none %}
+ match interface {{ rule_config.match.interface }}
+{% endif %}
+{% if rule_config.match.ip is defined and rule_config.match.ip.address is defined and rule_config.match.ip.address.access_list is defined and rule_config.match.ip.address.access_list is not none %}
+ match ip address {{ rule_config.match.ip.address.access_list }}
+{% endif %}
+{% if rule_config.match.ip is defined and rule_config.match.ip.address is defined and rule_config.match.ip.address.prefix_list is defined and rule_config.match.ip.address.prefix_list is not none %}
+ match ip address prefix-list {{ rule_config.match.ip.address.prefix_list }}
+{% endif %}
+{% if rule_config.match.ip is defined and rule_config.match.ip.nexthop is defined and rule_config.match.ip.nexthop.access_list is defined and rule_config.match.ip.nexthop.access_list is not none %}
+ match ip next-hop {{ rule_config.match.ip.nexthop.access_list }}
+{% endif %}
+{% if rule_config.match.ip is defined and rule_config.match.ip.nexthop is defined and rule_config.match.ip.nexthop.prefix_list is defined and rule_config.match.ip.nexthop.prefix_list is not none %}
+ match ip next-hop prefix-list {{ rule_config.match.ip.nexthop.prefix_list }}
+{% endif %}
+{% if rule_config.match.ip is defined and rule_config.match.ip.route_source is defined and rule_config.match.ip.route_source.access_list is defined and rule_config.match.ip.route_source.access_list is not none %}
+ match ip route-source {{ rule_config.match.ip.route_source.access_list }}
+{% endif %}
+{% if rule_config.match.ip is defined and rule_config.match.ip.route_source is defined and rule_config.match.ip.route_source.prefix_list is defined and rule_config.match.ip.route_source.prefix_list is not none %}
+ match ip route-source prefix-list {{ rule_config.match.ip.route_source.prefix_list }}
+{% endif %}
+{% if rule_config.match.ipv6 is defined and rule_config.match.ipv6.address is defined and rule_config.match.ipv6.address.access_list is defined and rule_config.match.ipv6.address.access_list is not none %}
+ match ipv6 address {{ rule_config.match.ipv6.address.access_list }}
+{% endif %}
+{% if rule_config.match.ipv6 is defined and rule_config.match.ipv6.address is defined and rule_config.match.ipv6.address.prefix_list is defined and rule_config.match.ipv6.address.prefix_list is not none %}
+ match ipv6 address prefix-list {{ rule_config.match.ipv6.address.prefix_list }}
+{% endif %}
+{% if rule_config.match.ipv6 is defined and rule_config.match.ipv6.nexthop is defined and rule_config.match.ipv6.nexthop is not none %}
+ match ipv6 next-hop {{ rule_config.match.ipv6.nexthop }}
+{% endif %}
+{% if rule_config.match.large_community is defined and rule_config.match.large_community.large_community_list is defined and rule_config.match.large_community.large_community_list is not none %}
+ match large-community {{ rule_config.match.large_community.large_community_list }}
+{% endif %}
+{% if rule_config.match.local_preference is defined and rule_config.match.local_preference is not none %}
+ match local-preference {{ rule_config.match.local_preference }}
+{% endif %}
+{% if rule_config.match.metric is defined and rule_config.match.metric is not none %}
+ match metric {{ rule_config.match.metric }}
+{% endif %}
+{% if rule_config.match.origin is defined and rule_config.match.origin is not none %}
+ match origin {{ rule_config.match.origin }}
+{% endif %}
+{% if rule_config.match.peer is defined and rule_config.match.peer is not none %}
+ match peer {{ rule_config.match.peer }}
+{% endif %}
+{% if rule_config.match.rpki is defined and rule_config.match.rpki is not none %}
+ match rpki {{ rule_config.match.rpki }}
+{% endif %}
+{% if rule_config.match.tag is defined and rule_config.match.tag is not none %}
+ match tag {{ rule_config.match.tag }}
+{% endif %}
+{% endif %}
+{% if rule_config.on_match is defined and rule_config.on_match is not none %}
+{% if rule_config.on_match.next is defined %}
+ on-match next
+{% endif %}
+{% if rule_config.on_match.goto is defined and rule_config.on_match.goto is not none %}
+ on-match goto {{ rule_config.on_match.goto }}
+{% endif %}
+{% endif %}
+{% if rule_config.set is defined and rule_config.set is not none %}
+{% if rule_config.set.aggregator is defined and rule_config.set.aggregator.as is defined and rule_config.set.aggregator.ip is defined %}
+ set aggregator as {{ rule_config.set.aggregator.as }} {{ rule_config.set.aggregator.ip }}
+{% endif %}
+{% if rule_config.set.as_path_exclude is defined and rule_config.set.as_path_exclude is not none %}
+ set as-path exclude {{ rule_config.set.as_path_exclude }}
+{% endif %}
+{% if rule_config.set.as_path_prepend is defined and rule_config.set.as_path_prepend is not none %}
+ set as-path prepend {{ rule_config.set.as_path_prepend }}
+{% endif %}
+{% if rule_config.set.atomic_aggregate is defined %}
+ set atomic-aggregate
+{% endif %}
+{% if rule_config.set.distance is defined and rule_config.set.distance is not none %}
+ set distance {{ rule_config.set.distance }}
+{% endif %}
+{% if rule_config.set.ip_next_hop is defined and rule_config.set.ip_next_hop is not none %}
+ set ip next-hop {{ rule_config.set.ip_next_hop }}
+{% endif %}
+{% if rule_config.set.ipv6_next_hop is defined and rule_config.set.ipv6_next_hop.global is defined and rule_config.set.ipv6_next_hop.global is not none %}
+ set ipv6 next-hop global {{ rule_config.set.ipv6_next_hop.global }}
+{% endif %}
+{% if rule_config.set.ipv6_next_hop is defined and rule_config.set.ipv6_next_hop.local is defined and rule_config.set.ipv6_next_hop.local is not none %}
+ set ipv6 next-hop local {{ rule_config.set.ipv6_next_hop.local }}
+{% endif %}
+{% if rule_config.set.large_community is defined and rule_config.set.large_community is not none %}
+ set large-community {{ rule_config.set.large_community }}
+{% endif %}
+{% if rule_config.set.local_preference is defined and rule_config.set.local_preference is not none %}
+ set local-preference {{ rule_config.set.local_preference }}
+{% endif %}
+{% if rule_config.set.metric is defined and rule_config.set.metric is not none %}
+ set metric {{ rule_config.set.metric }}
+{% endif %}
+{% if rule_config.set.metric_type is defined and rule_config.set.metric_type is not none %}
+ set metric-type {{ rule_config.set.metric_type }}
+{% endif %}
+{% if rule_config.set.origin is defined and rule_config.set.origin is not none %}
+ set origin {{ rule_config.set.origin }}
+{% endif %}
+{% if rule_config.set.originator_id is defined and rule_config.set.originator_id is not none %}
+ set originator-id {{ rule_config.set.originator_id }}
+{% endif %}
+{% if rule_config.set.src is defined and rule_config.set.src is not none %}
+ set src {{ rule_config.set.src }}
+{% endif %}
+{% if rule_config.set.tag is defined and rule_config.set.tag is not none %}
+ set tag {{ rule_config.set.tag }}
+{% endif %}
+{% if rule_config.set.weight is defined and rule_config.set.weight is not none %}
+ set weight {{ rule_config.set.weight }}
+{% endif %}
+{% endif %}
+{% endfor %}
+!
+{% endif %}
+{% endfor %}
+{% endif %}
+!
diff --git a/interface-definitions/include/policy-list-description.xml.i b/interface-definitions/include/policy-list-description.xml.i deleted file mode 100644 index a50278729..000000000 --- a/interface-definitions/include/policy-list-description.xml.i +++ /dev/null @@ -1,11 +0,0 @@ -<!-- included start from policy-list-description.xml.i --> -<leafNode name="description"> - <properties> - <help>Description for this policy</help> - <valueHelp> - <format>txt</format> - <description>Description for this policy</description> - </valueHelp> - </properties> -</leafNode> -<!-- included end --> diff --git a/interface-definitions/include/policy-list-rule-description.xml.i b/interface-definitions/include/policy-list-rule-description.xml.i deleted file mode 100644 index e22fb7c28..000000000 --- a/interface-definitions/include/policy-list-rule-description.xml.i +++ /dev/null @@ -1,11 +0,0 @@ -<!-- included start from policy-list-rule-description.xml.i --> -<leafNode name="description"> - <properties> - <help>Description for this rule</help> - <valueHelp> - <format>txt</format> - <description>Description for this rule</description> - </valueHelp> - </properties> -</leafNode> -<!-- included end --> diff --git a/interface-definitions/include/policy-list-action.xml.i b/interface-definitions/include/policy/action.xml.i index fddbd5a98..fddbd5a98 100644 --- a/interface-definitions/include/policy-list-action.xml.i +++ b/interface-definitions/include/policy/action.xml.i diff --git a/interface-definitions/include/policy/description.xml.i b/interface-definitions/include/policy/description.xml.i new file mode 100644 index 000000000..15a77cd66 --- /dev/null +++ b/interface-definitions/include/policy/description.xml.i @@ -0,0 +1,11 @@ +<!-- included start from policy/description.xml.i --> +<leafNode name="description"> + <properties> + <help>Description</help> + <valueHelp> + <format>txt</format> + <description>Description</description> + </valueHelp> + </properties> +</leafNode> +<!-- included end --> diff --git a/interface-definitions/include/policy/host.xml.i b/interface-definitions/include/policy/host.xml.i new file mode 100644 index 000000000..ac017c630 --- /dev/null +++ b/interface-definitions/include/policy/host.xml.i @@ -0,0 +1,14 @@ +<!-- include start from policy/host.xml.i --> +<leafNode name="host"> + <properties> + <help>Single host IP address to match</help> + <valueHelp> + <format>ipv4</format> + <description>Host address to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/inverse-mask.xml.i b/interface-definitions/include/policy/inverse-mask.xml.i new file mode 100644 index 000000000..cec69a81b --- /dev/null +++ b/interface-definitions/include/policy/inverse-mask.xml.i @@ -0,0 +1,14 @@ +<!-- include start from policy/inverse-mask.xml.i --> +<leafNode name="inverse-mask"> + <properties> + <help>Network/netmask to match (requires network be defined)</help> + <valueHelp> + <format>ipv4</format> + <description>Inverse-mask to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/network.xml.i b/interface-definitions/include/policy/network.xml.i new file mode 100644 index 000000000..f2aea6be8 --- /dev/null +++ b/interface-definitions/include/policy/network.xml.i @@ -0,0 +1,14 @@ +<!-- include start from policy/network.xml.i --> +<leafNode name="network"> + <properties> + <help>Network/netmask to match (requires inverse-mask be defined)</help> + <valueHelp> + <format>ipv4net</format> + <description>Inverse-mask to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index 7cf20d3de..3a769dea1 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -1,15 +1,15 @@ <?xml version="1.0"?> -<!-- Policy access|prefix|route-map lists --> <interfaceDefinition> - <node name="npolicy" owner="${vyos_conf_scripts_dir}/policy.py"> + <node name="policy" owner="${vyos_conf_scripts_dir}/policy.py"> <properties> + <priority>470</priority> <help>Routing policy</help> </properties> <children> <tagNode name="access-list"> <properties> <help>IP access-list filter</help> - <priority>470</priority> + <valueHelp> <format>u32:1-99</format> <description>IP standard access list</description> @@ -28,7 +28,7 @@ </valueHelp> </properties> <children> - #include <include/policy-list-description.xml.i> + #include <include/policy/description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this access-list</help> @@ -41,8 +41,8 @@ </constraint> </properties> <children> - #include <include/policy-list-action.xml.i> - #include <include/policy-list-rule-description.xml.i> + #include <include/policy/action.xml.i> + #include <include/policy/description.xml.i> <node name="destination"> <properties> <help>Destination network or address</help> @@ -54,42 +54,9 @@ <valueless/> </properties> </leafNode> - <leafNode name="host"> - <properties> - <help>Single host IP address to match</help> - <valueHelp> - <format>ipv4</format> - <description>Host address to match</description> - </valueHelp> - <constraint> - <validator name="ipv4-host"/> - </constraint> - </properties> - </leafNode> - <leafNode name="inverse-mask"> - <properties> - <help>Network/netmask to match (requires network be defined)</help> - <valueHelp> - <format>ipv4</format> - <description>Inverse-mask to match</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - </constraint> - </properties> - </leafNode> - <leafNode name="network"> - <properties> - <help>Network/netmask to match (requires inverse-mask be defined)</help> - <valueHelp> - <format>ipv4net</format> - <description>Inverse-mask to match</description> - </valueHelp> - <constraint> - <validator name="ip-prefix"/> - </constraint> - </properties> - </leafNode> + #include <include/policy/host.xml.i> + #include <include/policy/inverse-mask.xml.i> + #include <include/policy/network.xml.i> </children> </node> <node name="source"> @@ -103,42 +70,9 @@ <valueless/> </properties> </leafNode> - <leafNode name="host"> - <properties> - <help>Single host IP address to match</help> - <valueHelp> - <format>ipv4</format> - <description>Host address to match</description> - </valueHelp> - <constraint> - <validator name="ipv4-host"/> - </constraint> - </properties> - </leafNode> - <leafNode name="inverse-mask"> - <properties> - <help>Network/netmask to match (requires network be defined)</help> - <valueHelp> - <format>ipv4</format> - <description>Inverse-mask to match</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - </constraint> - </properties> - </leafNode> - <leafNode name="network"> - <properties> - <help>Network/netmask to match (requires inverse-mask be defined)</help> - <valueHelp> - <format>ipv4net</format> - <description>Inverse-mask to match</description> - </valueHelp> - <constraint> - <validator name="ip-prefix"/> - </constraint> - </properties> - </leafNode> + #include <include/policy/host.xml.i> + #include <include/policy/inverse-mask.xml.i> + #include <include/policy/network.xml.i> </children> </node> </children> @@ -148,14 +82,13 @@ <tagNode name="access-list6"> <properties> <help>IPv6 access-list filter</help> - <priority>470</priority> <valueHelp> <format>txt</format> <description>Name of IPv6 access-list</description> </valueHelp> </properties> <children> - #include <include/policy-list-description.xml.i> + #include <include/policy/description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this access-list6</help> @@ -168,8 +101,8 @@ </constraint> </properties> <children> - #include <include/policy-list-action.xml.i> - #include <include/policy-list-rule-description.xml.i> + #include <include/policy/action.xml.i> + #include <include/policy/description.xml.i> <node name="source"> <properties> <help>Source IPv6 network to match</help> @@ -208,14 +141,13 @@ <tagNode name="as-path-list"> <properties> <help>Border Gateway Protocol (BGP) autonomous system path filter</help> - <priority>470</priority> <valueHelp> <format>txt</format> <description>AS path list name</description> </valueHelp> </properties> <children> - #include <include/policy-list-description.xml.i> + #include <include/policy/description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this as-path-list</help> @@ -228,8 +160,8 @@ </constraint> </properties> <children> - #include <include/policy-list-action.xml.i> - #include <include/policy-list-rule-description.xml.i> + #include <include/policy/action.xml.i> + #include <include/policy/description.xml.i> <leafNode name="regex"> <properties> <help>Regular expression to match against an AS path</help> @@ -246,14 +178,13 @@ <tagNode name="community-list"> <properties> <help>Border Gateway Protocol (BGP) autonomous system path filter</help> - <priority>470</priority> <valueHelp> <format>txt</format> <description>Border Gateway Protocol (BGP) community-list filter</description> </valueHelp> </properties> <children> - #include <include/policy-list-description.xml.i> + #include <include/policy/description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this BGP community list</help> @@ -266,8 +197,8 @@ </constraint> </properties> <children> - #include <include/policy-list-action.xml.i> - #include <include/policy-list-rule-description.xml.i> + #include <include/policy/action.xml.i> + #include <include/policy/description.xml.i> <leafNode name="regex"> <properties> <help>Regular expression to match against a community list</help> @@ -291,7 +222,7 @@ </valueHelp> </properties> <children> - #include <include/policy-list-description.xml.i> + #include <include/policy/description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this BGP extended community list</help> @@ -304,8 +235,8 @@ </constraint> </properties> <children> - #include <include/policy-list-action.xml.i> - #include <include/policy-list-rule-description.xml.i> + #include <include/policy/action.xml.i> + #include <include/policy/description.xml.i> <leafNode name="regex"> <properties> <help>Regular expression to match against an extended community list</help> @@ -330,14 +261,13 @@ <tagNode name="large-community-list"> <properties> <help>Border Gateway Protocol (BGP) large-community-list filter</help> - <priority>470</priority> <valueHelp> <format>txt</format> <description>Border Gateway Protocol (BGP) large-community-list filter</description> </valueHelp> </properties> <children> - #include <include/policy-list-description.xml.i> + #include <include/policy/description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this BGP extended community list</help> @@ -350,8 +280,8 @@ </constraint> </properties> <children> - #include <include/policy-list-action.xml.i> - #include <include/policy-list-rule-description.xml.i> + #include <include/policy/action.xml.i> + #include <include/policy/description.xml.i> <leafNode name="regex"> <properties> <help>Regular expression to match against a large community list</help> @@ -368,14 +298,13 @@ <tagNode name="prefix-list"> <properties> <help>IP prefix-list filter</help> - <priority>470</priority> <valueHelp> <format>txt</format> <description>Prefix list name</description> </valueHelp> </properties> <children> - #include <include/policy-list-description.xml.i> + #include <include/policy/description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this prefix-list</help> @@ -388,8 +317,8 @@ </constraint> </properties> <children> - #include <include/policy-list-action.xml.i> - #include <include/policy-list-rule-description.xml.i> + #include <include/policy/action.xml.i> + #include <include/policy/description.xml.i> <leafNode name="ge"> <properties> <help>Prefix length to match a netmask greater than or equal to it</help> @@ -433,14 +362,13 @@ <tagNode name="prefix-list6"> <properties> <help>IPv6 prefix-list filter</help> - <priority>470</priority> <valueHelp> <format>txt</format> <description>Prefix list name</description> </valueHelp> </properties> <children> - #include <include/policy-list-description.xml.i> + #include <include/policy/description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this prefix-list6</help> @@ -453,8 +381,8 @@ </constraint> </properties> <children> - #include <include/policy-list-action.xml.i> - #include <include/policy-list-rule-description.xml.i> + #include <include/policy/action.xml.i> + #include <include/policy/description.xml.i> <leafNode name="ge"> <properties> <help>Prefix length to match a netmask greater than or equal to it</help> @@ -498,14 +426,17 @@ <tagNode name="route-map"> <properties> <help>IP route-map</help> - <priority>470</priority> <valueHelp> <format>txt</format> <description>Route map name</description> </valueHelp> + <constraint> + <regex>^[-a-zA-Z0-9.]+$</regex> + </constraint> + <constraintErrorMessage>Route-map name can only contain alpha-numeric letters and a hyphen</constraintErrorMessage> </properties> <children> - #include <include/policy-list-description.xml.i> + #include <include/policy/description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this route-map</help> @@ -518,7 +449,7 @@ </constraint> </properties> <children> - #include <include/policy-list-action.xml.i> + #include <include/policy/action.xml.i> <leafNode name="call"> <properties> <help>Call another route-map on match</help> @@ -540,7 +471,7 @@ </valueHelp> </properties> </leafNode> - #include <include/policy-list-rule-description.xml.i> + #include <include/policy/description.xml.i> <node name="match"> <properties> <help>Route parameters to match</help> @@ -759,11 +690,11 @@ <properties> <help>IPv6 next-hop of route to match</help> <valueHelp> - <format>ipv4</format> - <description>Peer IP address</description> + <format>ipv6</format> + <description>Nexthop IPv6 address</description> </valueHelp> <constraint> - <validator name="ipv4-address"/> + <validator name="ipv6-address"/> </constraint> </properties> </leafNode> @@ -962,6 +893,7 @@ <leafNode name="atomic-aggregate"> <properties> <help>Border Gateway Protocol (BGP) atomic aggregate attribute</help> + <valueless/> </properties> </leafNode> <leafNode name="bgp-extcommunity-rt"> @@ -1198,7 +1130,7 @@ <description>Orignator IP address</description> </valueHelp> <constraint> - <validator name="ipv4-host"/> + <validator name="ipv4-address"/> </constraint> </properties> </leafNode> @@ -1214,8 +1146,8 @@ <description>IPv6 address</description> </valueHelp> <constraint> - <validator name="ipv4-host"/> - <validator name="ipv6-host"/> + <validator name="ipv4-address"/> + <validator name="ipv6-address"/> </constraint> </properties> </leafNode> diff --git a/python/vyos/template.py b/python/vyos/template.py index 7810f5edd..3fbb33acb 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -346,3 +346,18 @@ def get_dhcp_router(interface): if 'option routers' in line: (_, _, address) = line.split() return address.rstrip(';') + +@register_filter('natural_sort') +def natural_sort(iterable): + import re + from jinja2.runtime import Undefined + + if isinstance(iterable, Undefined) or iterable is None: + return list() + + def convert(text): + return int(text) if text.isdigit() else text.lower() + def alphanum_key(key): + return [convert(c) for c in re.split('([0-9]+)', str(key))] + + return sorted(iterable, key=alphanum_key) diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 57c1bb088..59425b789 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -767,9 +767,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): '10' : { 'action' : 'deny', 'set' : { -# Disabled b/c of https://phabricator.vyos.net/T3479 -# 'aggregator-as' : '1234567890', -# 'aggregator-ip' : '10.255.255.0', + 'aggregator-as' : '1234567890', + 'aggregator-ip' : '10.255.255.0', 'as-path-exclude' : '1234', 'as-path-prepend' : '1234567890 987654321', 'atomic-aggregate' : '', @@ -1036,12 +1035,12 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): tmp += 'atomic-aggregate' elif 'distance' in rule_config['set']: tmp += 'distance ' + rule_config['set']['distance'] + elif 'ip-next-hop' in rule_config['set']: + tmp += 'ip next-hop ' + rule_config['set']['ip-next-hop'] elif 'ipv6-next-hop-global' in rule_config['set']: tmp += 'ipv6 next-hop global ' + rule_config['set']['ipv6-next-hop-global'] elif 'ipv6-next-hop-local' in rule_config['set']: tmp += 'ipv6 next-hop local ' + rule_config['set']['ipv6-next-hop-local'] - elif 'ip-next-hop' in rule_config['set']: - tmp += 'ip next-hop ' + rule_config['set']['ip-next-hop'] elif 'large-community' in rule_config['set']: tmp += 'large-community ' + rule_config['set']['large-community'] elif 'local-preference' in rule_config['set']: diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index 94a020e7b..f2e9010e3 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -20,47 +20,46 @@ from sys import exit from vyos.config import Config from vyos.configdict import dict_merge -from vyos.template import render from vyos.template import render_to_string -from vyos.util import call from vyos.util import dict_search from vyos import ConfigError from vyos import frr from vyos import airbag -from pprint import pprint airbag.enable() -config_file = r'/tmp/policy.frr' -frr_daemon = 'zebra' - -DEBUG = os.path.exists('/tmp/policy.debug') -if DEBUG: - import logging - lg = logging.getLogger("vyos.frr") - lg.setLevel(logging.DEBUG) - ch = logging.StreamHandler() - lg.addHandler(ch) - def get_config(config=None): if config: conf = config else: conf = Config() - base = ['npolicy'] - policy = conf.get_config_dict(base, key_mangling=('-', '_')) - # Bail out early if configuration tree does not exist - if not conf.exists(base): - return policy - - pprint(policy) - exit(1) + base = ['policy'] + policy = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True) return policy def verify(policy): if not policy: return None + if 'access-list' in policy: + for acl, acl_config in policy['access-list'].items(): + if 'rule' not in acl_config: + continue + + for rule, rule_config in acl_config['rule'].items(): + if 'source' not in rule_config: + raise ConfigError(f'Source must be specified for rule {rule} '\ + f'for access-list {acl}!') + if 'action' not in rule_config: + raise ConfigError(f'Action must be specified for rule {rule} '\ + f'for access-list {acl}!') + + if int(acl) not in range(100, 200) or int(acl) not in range(2000, 2700): + if 'destination' not in rule_config: + raise ConfigError(f'Destination must be specified for rule {rule} '\ + f'for access-list {acl}!') + return None def generate(policy): @@ -68,41 +67,42 @@ def generate(policy): policy['new_frr_config'] = '' return None - # render(config) not needed, its only for debug - # render(config_file, 'frr/policy.frr.tmpl', policy) - # policy['new_frr_config'] = render_to_string('frr/policy.frr.tmpl') - + policy['new_frr_config'] = render_to_string('frr/policy.frr.tmpl', policy) return None def apply(policy): + bgp_daemon = 'bgpd' + zebra_daemon = 'zebra' + # Save original configuration prior to starting any commit actions - # frr_cfg = frr.FRRConfig() - # frr_cfg.load_configuration(frr_daemon) - # frr_cfg.modify_section(f'ip', '') - # frr_cfg.add_before(r'(line vty)', policy['new_frr_config']) - - # Debugging - if DEBUG: - from pprint import pprint - print('') - print('--------- DEBUGGING ----------') - pprint(dir(frr_cfg)) - print('Existing config:\n') - for line in frr_cfg.original_config: - print(line) - print(f'Replacement config:\n') - print(f'{policy["new_frr_config"]}') - print(f'Modified config:\n') - print(f'{frr_cfg}') - - # frr_cfg.commit_configuration(frr_daemon) + frr_cfg = frr.FRRConfig() + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(bgp_daemon) + frr_cfg.modify_section(r'^bgp as-path access-list .*') + frr_cfg.modify_section(r'^bgp community-list .*') + frr_cfg.modify_section(r'^bgp extcommunity-list .*') + frr_cfg.modify_section(r'^bgp large-community-list .*') + frr_cfg.add_before('^line vty', policy['new_frr_config']) + frr_cfg.commit_configuration(bgp_daemon) + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(r'^access-list .*') + frr_cfg.modify_section(r'^ipv6 access-list .*') + frr_cfg.modify_section(r'^ip prefix-list .*') + frr_cfg.modify_section(r'^ipv6 prefix-list .*') + frr_cfg.add_before('^line vty', policy['new_frr_config']) + frr_cfg.commit_configuration(zebra_daemon) # If FRR config is blank, rerun the blank commit x times due to frr-reload # behavior/bug not properly clearing out on one commit. - # if policy['new_frr_config'] == '': - # for a in range(5): - # frr_cfg.commit_configuration(frr_daemon) + if policy['new_frr_config'] == '': + for a in range(5): + frr_cfg.commit_configuration(zebra_daemon) + # Save configuration to /run/frr/config/frr.conf + frr.save_configuration() return None |