diff --git a/data/templates/frr/policy.frr.j2 b/data/templates/frr/policy.frr.j2 index ed5876ae9..c28633f6f 100644 --- a/data/templates/frr/policy.frr.j2 +++ b/data/templates/frr/policy.frr.j2 @@ -1,377 +1,380 @@ {% if access_list is vyos_defined %} {% for acl, acl_config in access_list.items() | natural_sort %} {% if acl_config.description is vyos_defined %} access-list {{ acl }} remark {{ acl_config.description }} {% endif %} {% if acl_config.rule is vyos_defined %} {% for rule, rule_config in acl_config.rule.items() | natural_sort %} {% set ip = '' %} {% set src = '' %} {% set src_mask = '' %} {% if rule_config.source.any is vyos_defined %} {% set src = 'any' %} {% elif rule_config.source.host is vyos_defined %} {% set src = 'host ' ~ rule_config.source.host %} {% elif rule_config.source.network is vyos_defined %} {% 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.any is vyos_defined %} {% set dst = 'any' %} {% elif rule_config.destination.host is vyos_defined %} {% set dst = 'host ' ~ rule_config.destination.host %} {% elif rule_config.destination.network is vyos_defined %} {% 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 access_list6 is vyos_defined %} {% for acl, acl_config in access_list6.items() | natural_sort %} {% if acl_config.description is vyos_defined %} ipv6 access-list {{ acl }} remark {{ acl_config.description }} {% endif %} {% if acl_config.rule is vyos_defined %} {% for rule, rule_config in acl_config.rule.items() | natural_sort %} {% set src = '' %} {% if rule_config.source.any is vyos_defined %} {% set src = 'any' %} {% elif rule_config.source.network is vyos_defined %} {% 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 vyos_defined }} {% endfor %} {% endif %} {% endfor %} {% endif %} ! {% if as_path_list is vyos_defined %} {% for acl, acl_config in as_path_list.items() | natural_sort %} {% if acl_config.rule is vyos_defined %} {% for rule, rule_config in acl_config.rule.items() | natural_sort %} bgp as-path access-list {{ acl }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.regex }} {% endfor %} {% endif %} {% endfor %} {% endif %} ! {% if community_list is vyos_defined %} {% for list, list_config in community_list.items() | natural_sort %} {% if list_config.rule is vyos_defined %} {% 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 extcommunity_list is vyos_defined %} {% for list, list_config in extcommunity_list.items() | natural_sort %} {% if list_config.rule is vyos_defined %} {% 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 large_community_list is vyos_defined %} {% for list, list_config in large_community_list.items() | natural_sort %} {% if list_config.rule is vyos_defined %} {% 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 prefix_list is vyos_defined %} {% for prefix_list, prefix_list_config in prefix_list.items() | natural_sort %} {% if prefix_list_config.description is vyos_defined %} ip prefix-list {{ prefix_list }} description {{ prefix_list_config.description }} {% endif %} {% if prefix_list_config.rule is vyos_defined %} {% for rule, rule_config in prefix_list_config.rule.items() | natural_sort %} {% if rule_config.prefix is vyos_defined %} ip prefix-list {{ prefix_list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.prefix }} {{ 'ge ' ~ rule_config.ge if rule_config.ge is vyos_defined }} {{ 'le ' ~ rule_config.le if rule_config.le is vyos_defined }} {% endif %} {% endfor %} {% endif %} {% endfor %} {% endif %} ! {% if prefix_list6 is vyos_defined %} {% for prefix_list, prefix_list_config in prefix_list6.items() | natural_sort %} {% if prefix_list_config.description is vyos_defined %} ipv6 prefix-list {{ prefix_list }} description {{ prefix_list_config.description }} {% endif %} {% if prefix_list_config.rule is vyos_defined %} {% for rule, rule_config in prefix_list_config.rule.items() | natural_sort %} {% if rule_config.prefix is vyos_defined %} ipv6 prefix-list {{ prefix_list }} seq {{ rule }} {{ rule_config.action }} {{ rule_config.prefix }} {{ 'ge ' ~ rule_config.ge if rule_config.ge is vyos_defined }} {{ 'le ' ~ rule_config.le if rule_config.le is vyos_defined }} {% endif %} {% endfor %} {% endif %} {% endfor %} {% endif %} ! {% if route_map is vyos_defined %} {% for route_map, route_map_config in route_map.items() | natural_sort %} {% if route_map_config.rule is vyos_defined %} {% 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 vyos_defined %} call {{ rule_config.call }} {% endif %} {% if rule_config.continue is vyos_defined %} on-match goto {{ rule_config.continue }} {% endif %} {% if rule_config.description is vyos_defined %} description {{ rule_config.description }} {% endif %} {% if rule_config.match is vyos_defined %} {% if rule_config.match.as_path is vyos_defined %} match as-path {{ rule_config.match.as_path }} {% endif %} {% if rule_config.match.community.community_list is vyos_defined %} match community {{ rule_config.match.community.community_list }} {{ 'exact-match' if rule_config.match.community.exact_match is vyos_defined }} {% endif %} {% if rule_config.match.extcommunity is vyos_defined %} match extcommunity {{ rule_config.match.extcommunity }} {% endif %} {% if rule_config.match.evpn.default_route is vyos_defined %} match evpn default-route {% endif %} {% if rule_config.match.evpn.rd is vyos_defined %} match evpn rd {{ rule_config.match.evpn.rd }} {% endif %} {% if rule_config.match.evpn.route_type is vyos_defined %} match evpn route-type {{ rule_config.match.evpn.route_type }} {% endif %} {% if rule_config.match.evpn.vni is vyos_defined %} match evpn vni {{ rule_config.match.evpn.vni }} {% endif %} {% if rule_config.match.interface is vyos_defined %} match interface {{ rule_config.match.interface }} {% endif %} {% if rule_config.match.ip.address.access_list is vyos_defined %} match ip address {{ rule_config.match.ip.address.access_list }} {% endif %} {% if rule_config.match.ip.address.prefix_list is vyos_defined %} match ip address prefix-list {{ rule_config.match.ip.address.prefix_list }} {% endif %} {% if rule_config.match.ip.address.prefix_len is vyos_defined %} match ip address prefix-len {{ rule_config.match.ip.address.prefix_len }} {% endif %} {% if rule_config.match.ip.nexthop.access_list is vyos_defined %} match ip next-hop {{ rule_config.match.ip.nexthop.access_list }} {% endif %} {% if rule_config.match.ip.nexthop.address is vyos_defined %} match ip next-hop address {{ rule_config.match.ip.nexthop.address }} {% endif %} {% if rule_config.match.ip.nexthop.prefix_len is vyos_defined %} match ip next-hop prefix-len {{ rule_config.match.ip.nexthop.prefix_len }} {% endif %} {% if rule_config.match.ip.nexthop.prefix_list is vyos_defined %} match ip next-hop prefix-list {{ rule_config.match.ip.nexthop.prefix_list }} {% endif %} {% if rule_config.match.ip.nexthop.type is vyos_defined %} match ip next-hop type {{ rule_config.match.ip.nexthop.type }} {% endif %} {% if rule_config.match.ip.route_source.access_list is vyos_defined %} match ip route-source {{ rule_config.match.ip.route_source.access_list }} {% endif %} {% if rule_config.match.ip.route_source.prefix_list is vyos_defined %} match ip route-source prefix-list {{ rule_config.match.ip.route_source.prefix_list }} {% endif %} {% if rule_config.match.ipv6.address.access_list is vyos_defined %} match ipv6 address {{ rule_config.match.ipv6.address.access_list }} {% endif %} {% if rule_config.match.ipv6.address.prefix_list is vyos_defined %} match ipv6 address prefix-list {{ rule_config.match.ipv6.address.prefix_list }} {% endif %} {% if rule_config.match.ipv6.address.prefix_len is vyos_defined %} match ipv6 address prefix-len {{ rule_config.match.ipv6.address.prefix_len }} {% endif %} {% if rule_config.match.ipv6.nexthop.address is vyos_defined %} match ipv6 next-hop address {{ rule_config.match.ipv6.nexthop.address }} {% endif %} {% if rule_config.match.ipv6.nexthop.access_list is vyos_defined %} match ipv6 next-hop {{ rule_config.match.ipv6.nexthop.access_list }} {% endif %} {% if rule_config.match.ipv6.nexthop.prefix_list is vyos_defined %} match ipv6 next-hop prefix-list {{ rule_config.match.ipv6.nexthop.prefix_list }} {% endif %} {% if rule_config.match.ipv6.nexthop.type is vyos_defined %} match ipv6 next-hop type {{ rule_config.match.ipv6.nexthop.type }} {% endif %} {% if rule_config.match.large_community.large_community_list is vyos_defined %} match large-community {{ rule_config.match.large_community.large_community_list }} {% endif %} {% if rule_config.match.local_preference is vyos_defined %} match local-preference {{ rule_config.match.local_preference }} {% endif %} {% if rule_config.match.metric is vyos_defined %} match metric {{ rule_config.match.metric }} {% endif %} {% if rule_config.match.origin is vyos_defined %} match origin {{ rule_config.match.origin }} {% endif %} {% if rule_config.match.peer is vyos_defined %} match peer {{ rule_config.match.peer }} {% endif %} {% if rule_config.match.protocol is vyos_defined %} {% set source_protocol = 'ospf6' if rule_config.match.protocol == 'ospfv3' else rule_config.match.protocol %} match source-protocol {{ source_protocol }} {% endif %} {% if rule_config.match.rpki is vyos_defined %} match rpki {{ rule_config.match.rpki }} {% endif %} +{% if rule_config.match.source_vrf is vyos_defined %} + match source-vrf {{ rule_config.match.source_vrf }} +{% endif %} {% if rule_config.match.tag is vyos_defined %} match tag {{ rule_config.match.tag }} {% endif %} {% endif %} {% if rule_config.on_match.next is vyos_defined %} on-match next {% endif %} {% if rule_config.on_match.goto is vyos_defined %} on-match goto {{ rule_config.on_match.goto }} {% endif %} {% if rule_config.set is vyos_defined %} {% if rule_config.set.aggregator.as is vyos_defined and rule_config.set.aggregator.ip is vyos_defined %} set aggregator as {{ rule_config.set.aggregator.as }} {{ rule_config.set.aggregator.ip }} {% endif %} {% if rule_config.set.as_path.exclude is vyos_defined %} set as-path exclude {{ rule_config.set.as_path.exclude }} {% endif %} {% if rule_config.set.as_path.prepend is vyos_defined %} set as-path prepend {{ rule_config.set.as_path.prepend }} {% endif %} {% if rule_config.set.as_path.prepend_last_as is vyos_defined %} set as-path prepend last-as {{ rule_config.set.as_path.prepend_last_as }} {% endif %} {% if rule_config.set.atomic_aggregate is vyos_defined %} set atomic-aggregate {% endif %} {% if rule_config.set.community.delete is vyos_defined %} set comm-list {{ rule_config.set.community.delete }} delete {% endif %} {% if rule_config.set.community.replace is vyos_defined %} set community {{ rule_config.set.community.replace | join(' ') | replace("local-as" , "local-AS") }} {% endif %} {% if rule_config.set.community.add is vyos_defined %} set community {{ rule_config.set.community.add | join(' ') | replace("local-as" , "local-AS") }} additive {% endif %} {% if rule_config.set.community.none is vyos_defined %} set community none {% endif %} {% if rule_config.set.distance is vyos_defined %} set distance {{ rule_config.set.distance }} {% endif %} {% if rule_config.set.evpn.gateway.ipv4 is vyos_defined %} set evpn gateway-ip ipv4 {{ rule_config.set.evpn.gateway.ipv4 }} {% endif %} {% if rule_config.set.evpn.gateway.ipv6 is vyos_defined %} set evpn gateway-ip ipv6 {{ rule_config.set.evpn.gateway.ipv6 }} {% endif %} {% if rule_config.set.extcommunity.bandwidth is vyos_defined %} set extcommunity bandwidth {{ rule_config.set.extcommunity.bandwidth }} {{ 'non-transitive' if rule_config.set.extcommunity.bandwidth_non_transitive is vyos_defined }} {% endif %} {% if rule_config.set.extcommunity.rt is vyos_defined %} set extcommunity rt {{ rule_config.set.extcommunity.rt | join(' ') }} {% endif %} {% if rule_config.set.extcommunity.soo is vyos_defined %} set extcommunity soo {{ rule_config.set.extcommunity.soo | join(' ') }} {% endif %} {% if rule_config.set.extcommunity.none is vyos_defined %} set extcommunity none {% endif %} {% if rule_config.set.ip_next_hop is vyos_defined %} set ip next-hop {{ rule_config.set.ip_next_hop }} {% endif %} {% if rule_config.set.ipv6_next_hop.global is vyos_defined %} set ipv6 next-hop global {{ rule_config.set.ipv6_next_hop.global }} {% endif %} {% if rule_config.set.ipv6_next_hop.local is vyos_defined %} set ipv6 next-hop local {{ rule_config.set.ipv6_next_hop.local }} {% endif %} {% if rule_config.set.ipv6_next_hop.peer_address is vyos_defined %} set ipv6 next-hop peer-address {% endif %} {% if rule_config.set.ipv6_next_hop.prefer_global is vyos_defined %} set ipv6 next-hop prefer-global {% endif %} {% if rule_config.set.l3vpn_nexthop.encapsulation.gre is vyos_defined %} set l3vpn next-hop encapsulation gre {% endif %} {% if rule_config.set.large_community.replace is vyos_defined %} set large-community {{ rule_config.set.large_community.replace | join(' ') }} {% endif %} {% if rule_config.set.large_community.add is vyos_defined %} set large-community {{ rule_config.set.large_community.add | join(' ') }} additive {% endif %} {% if rule_config.set.large_community.none is vyos_defined %} set large-community none {% endif %} {% if rule_config.set.large_community.delete is vyos_defined %} set large-comm-list {{ rule_config.set.large_community.delete }} delete {% endif %} {% if rule_config.set.local_preference is vyos_defined %} set local-preference {{ rule_config.set.local_preference }} {% endif %} {% if rule_config.set.metric is vyos_defined %} set metric {{ rule_config.set.metric }} {% endif %} {% if rule_config.set.metric_type is vyos_defined %} set metric-type {{ rule_config.set.metric_type }} {% endif %} {% if rule_config.set.origin is vyos_defined %} set origin {{ rule_config.set.origin }} {% endif %} {% if rule_config.set.originator_id is vyos_defined %} set originator-id {{ rule_config.set.originator_id }} {% endif %} {% if rule_config.set.src is vyos_defined %} set src {{ rule_config.set.src }} {% endif %} {% if rule_config.set.table is vyos_defined %} set table {{ rule_config.set.table }} {% endif %} {% if rule_config.set.tag is vyos_defined %} set tag {{ rule_config.set.tag }} {% endif %} {% if rule_config.set.weight is vyos_defined %} set weight {{ rule_config.set.weight }} {% endif %} {% endif %} exit ! {% endfor %} {% endif %} {% endfor %} {% endif %} diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index cbab6173f..faba91ef0 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -1,1622 +1,1636 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="policy" owner="${vyos_conf_scripts_dir}/policy.py"> <properties> <priority>200</priority> <help>Routing policy</help> </properties> <children> <tagNode name="access-list"> <properties> <help>IP access-list filter</help> <valueHelp> <format>u32:1-99</format> <description>IP standard access list</description> </valueHelp> <valueHelp> <format>u32:100-199</format> <description>IP extended access list</description> </valueHelp> <valueHelp> <format>u32:1300-1999</format> <description>IP standard access list (expanded range)</description> </valueHelp> <valueHelp> <format>u32:2000-2699</format> <description>IP extended access list (expanded range)</description> </valueHelp> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this access-list</help> <valueHelp> <format>u32:1-65535</format> <description>Access-list rule number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> <children> #include <include/policy/action.xml.i> #include <include/generic-description.xml.i> <node name="destination"> <properties> <help>Destination network or address</help> </properties> <children> <leafNode name="any"> <properties> <help>Any IP address to match</help> <valueless/> </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"> <properties> <help>Source network or address to match</help> </properties> <children> <leafNode name="any"> <properties> <help>Any IP address to match</help> <valueless/> </properties> </leafNode> #include <include/policy/host.xml.i> #include <include/policy/inverse-mask.xml.i> #include <include/policy/network.xml.i> </children> </node> </children> </tagNode> </children> </tagNode> <tagNode name="access-list6"> <properties> <help>IPv6 access-list filter</help> <valueHelp> <format>txt</format> <description>Name of IPv6 access-list</description> </valueHelp> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this access-list6</help> <valueHelp> <format>u32:1-65535</format> <description>Access-list6 rule number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> <children> #include <include/policy/action.xml.i> #include <include/generic-description.xml.i> <node name="source"> <properties> <help>Source IPv6 network to match</help> </properties> <children> <leafNode name="any"> <properties> <help>Any IP address to match</help> <valueless/> </properties> </leafNode> <leafNode name="exact-match"> <properties> <help>Exact match of the network prefixes</help> <valueless/> </properties> </leafNode> <leafNode name="network"> <properties> <help>Network/netmask to match</help> <valueHelp> <format>ipv6net</format> <description>IPv6 address and prefix length</description> </valueHelp> <constraint> <validator name="ipv6-prefix"/> </constraint> </properties> </leafNode> </children> </node> </children> </tagNode> </children> </tagNode> <tagNode name="as-path-list"> <properties> <help>Add a BGP autonomous system path filter</help> <valueHelp> <format>txt</format> <description>AS path list name</description> </valueHelp> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this as-path-list</help> <valueHelp> <format>u32:1-65535</format> <description>AS path list rule number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> <children> #include <include/policy/action.xml.i> #include <include/generic-description.xml.i> <leafNode name="regex"> <properties> <help>Regular expression to match against an AS path</help> <valueHelp> <format>txt</format> <description>AS path regular expression (ex: "64501 64502")</description> </valueHelp> </properties> </leafNode> </children> </tagNode> </children> </tagNode> <tagNode name="community-list"> <properties> <help>Add a BGP community list entry</help> <valueHelp> <format>txt</format> <description>BGP community-list name</description> </valueHelp> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this BGP community list</help> <valueHelp> <format>u32:1-65535</format> <description>Community-list rule number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> <children> #include <include/policy/action.xml.i> #include <include/generic-description.xml.i> <leafNode name="regex"> <properties> <help>Regular expression to match against a community-list</help> <completionHelp> <list>local-AS no-advertise no-export internet graceful-shutdown accept-own-nexthop accept-own route-filter-translated-v4 route-filter-v4 route-filter-translated-v6 route-filter-v6 llgr-stale no-llgr blackhole no-peer additive</list> </completionHelp> <valueHelp> <format><aa:nn></format> <description>Community number in AA:NN format where AA and NN are (0-65535)</description> </valueHelp> <valueHelp> <format>local-AS</format> <description>Well-known communities value NO_EXPORT_SUBCONFED 0xFFFFFF03</description> </valueHelp> <valueHelp> <format>no-advertise</format> <description>Well-known communities value NO_ADVERTISE 0xFFFFFF02</description> </valueHelp> <valueHelp> <format>no-export</format> <description>Well-known communities value NO_EXPORT 0xFFFFFF01</description> </valueHelp> <valueHelp> <format>internet</format> <description>Well-known communities value 0</description> </valueHelp> <valueHelp> <format>graceful-shutdown</format> <description>Well-known communities value GRACEFUL_SHUTDOWN 0xFFFF0000</description> </valueHelp> <valueHelp> <format>accept-own-nexthop</format> <description>Well-known communities value ACCEPT_OWN_NEXTHOP 0xFFFF0008</description> </valueHelp> <valueHelp> <format>accept-own</format> <description>Well-known communities value ACCEPT_OWN 0xFFFF0001 65535:1</description> </valueHelp> <valueHelp> <format>route-filter-translated-v4</format> <description>Well-known communities value ROUTE_FILTER_TRANSLATED_v4 0xFFFF0002 65535:2</description> </valueHelp> <valueHelp> <format>route-filter-v4</format> <description>Well-known communities value ROUTE_FILTER_v4 0xFFFF0003 65535:3</description> </valueHelp> <valueHelp> <format>route-filter-translated-v6</format> <description>Well-known communities value ROUTE_FILTER_TRANSLATED_v6 0xFFFF0004 65535:4</description> </valueHelp> <valueHelp> <format>route-filter-v6</format> <description>Well-known communities value ROUTE_FILTER_v6 0xFFFF0005 65535:5</description> </valueHelp> <valueHelp> <format>llgr-stale</format> <description>Well-known communities value LLGR_STALE 0xFFFF0006 65535:6</description> </valueHelp> <valueHelp> <format>no-llgr</format> <description>Well-known communities value NO_LLGR 0xFFFF0007 65535:7</description> </valueHelp> <valueHelp> <format>blackhole</format> <description>Well-known communities value BLACKHOLE 0xFFFF029A 65535:666</description> </valueHelp> <valueHelp> <format>no-peer</format> <description>Well-known communities value NOPEER 0xFFFFFF04 65535:65284</description> </valueHelp> <valueHelp> <format>additive</format> <description>New value is appended to the existing value</description> </valueHelp> </properties> </leafNode> </children> </tagNode> </children> </tagNode> <tagNode name="extcommunity-list"> <properties> <help>Add a BGP extended community list entry</help> <valueHelp> <format>txt</format> <description>BGP extended community-list name</description> </valueHelp> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> </constraint> <constraintErrorMessage>Should be an alphanumeric name</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this BGP extended community list</help> <valueHelp> <format>u32:1-65535</format> <description>Extended community-list rule number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> <children> #include <include/policy/action.xml.i> #include <include/generic-description.xml.i> <leafNode name="regex"> <properties> <help>Regular expression to match against an extended community list</help> <valueHelp> <format><aa:nn:nn></format> <description>Extended community list regular expression</description> </valueHelp> <valueHelp> <format><rt aa:nn:nn></format> <description>Route Target regular expression</description> </valueHelp> <valueHelp> <format><soo aa:nn:nn></format> <description>Site of Origin regular expression</description> </valueHelp> </properties> </leafNode> </children> </tagNode> </children> </tagNode> <tagNode name="large-community-list"> <properties> <help>Add a BGP large community list entry</help> <valueHelp> <format>txt</format> <description>BGP large-community-list name</description> </valueHelp> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> </constraint> <constraintErrorMessage>Should be an alphanumeric name</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this BGP extended community list</help> <valueHelp> <format>u32:1-65535</format> <description>Large community-list rule number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> <children> #include <include/policy/action.xml.i> #include <include/generic-description.xml.i> <leafNode name="regex"> <properties> <help>Regular expression to match against a large community list</help> <valueHelp> <format>ASN:NN:NN</format> <description>BGP large-community-list filter</description> </valueHelp> <valueHelp> <format>IP:NN:NN</format> <description>BGP large-community-list filter (IPv4 address format)</description> </valueHelp> <constraint> <validator name="bgp-large-community-list"/> </constraint> <constraintErrorMessage>Malformed large-community-list</constraintErrorMessage> </properties> </leafNode> </children> </tagNode> </children> </tagNode> <tagNode name="prefix-list"> <properties> <help>IP prefix-list filter</help> <valueHelp> <format>txt</format> <description>Name of IPv4 prefix-list</description> </valueHelp> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> </constraint> <constraintErrorMessage>Name of prefix-list can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this prefix-list</help> <valueHelp> <format>u32:1-65535</format> <description>Prefix-list rule number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> <children> #include <include/policy/action.xml.i> #include <include/generic-description.xml.i> <leafNode name="ge"> <properties> <help>Prefix length to match a netmask greater than or equal to it</help> <valueHelp> <format>u32:0-32</format> <description>Netmask greater than length</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-32"/> </constraint> </properties> </leafNode> <leafNode name="le"> <properties> <help>Prefix length to match a netmask less than or equal to it</help> <valueHelp> <format>u32:0-32</format> <description>Netmask less than length</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-32"/> </constraint> </properties> </leafNode> <leafNode name="prefix"> <properties> <help>Prefix to match</help> <valueHelp> <format>ipv4net</format> <description>Prefix to match against</description> </valueHelp> <constraint> <validator name="ipv4-prefix"/> </constraint> </properties> </leafNode> </children> </tagNode> </children> </tagNode> <tagNode name="prefix-list6"> <properties> <help>IPv6 prefix-list filter</help> <valueHelp> <format>txt</format> <description>Name of IPv6 prefix-list</description> </valueHelp> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> </constraint> <constraintErrorMessage>Name of prefix-list6 can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this prefix-list6</help> <valueHelp> <format>u32:1-65535</format> <description>Prefix-list rule number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> <children> #include <include/policy/action.xml.i> #include <include/generic-description.xml.i> <leafNode name="ge"> <properties> <help>Prefix length to match a netmask greater than or equal to it</help> <valueHelp> <format>u32:0-128</format> <description>Netmask greater than length</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-128"/> </constraint> </properties> </leafNode> <leafNode name="le"> <properties> <help>Prefix length to match a netmask less than or equal to it</help> <valueHelp> <format>u32:0-128</format> <description>Netmask less than length</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-128"/> </constraint> </properties> </leafNode> <leafNode name="prefix"> <properties> <help>Prefix to match</help> <valueHelp> <format>ipv6net</format> <description>IPv6 prefix</description> </valueHelp> <constraint> <validator name="ipv6-prefix"/> </constraint> </properties> </leafNode> </children> </tagNode> </children> </tagNode> <tagNode name="route-map"> <properties> <help>IP route-map</help> <valueHelp> <format>txt</format> <description>Route map name</description> </valueHelp> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> </constraint> <constraintErrorMessage>Name of route-map can only contain alpha-numeric letters, hyphen and underscores</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> <help>Rule for this route-map</help> <valueHelp> <format>u32:1-65535</format> <description>Route-map rule number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> <children> #include <include/policy/action.xml.i> <leafNode name="call"> <properties> <help>Call another route-map on match</help> <valueHelp> <format>txt</format> <description>Route map name</description> </valueHelp> <completionHelp> <path>policy route-map</path> </completionHelp> </properties> </leafNode> <leafNode name="continue"> <properties> <help>Jump to a different rule in this route-map on a match</help> <valueHelp> <format>u32:1-65535</format> <description>Rule number</description> </valueHelp> </properties> </leafNode> #include <include/generic-description.xml.i> <node name="match"> <properties> <help>Route parameters to match</help> </properties> <children> <leafNode name="as-path"> <properties> <help>BGP as-path-list to match</help> <completionHelp> <path>policy as-path-list</path> </completionHelp> </properties> </leafNode> <node name="community"> <properties> <help>BGP community-list to match</help> </properties> <children> <leafNode name="community-list"> <properties> <help>BGP community-list to match</help> <completionHelp> <path>policy community-list</path> </completionHelp> </properties> </leafNode> <leafNode name="exact-match"> <properties> <help>Community-list to exactly match</help> <valueless/> </properties> </leafNode> </children> </node> <node name="evpn"> <properties> <help>Ethernet Virtual Private Network</help> </properties> <children> <leafNode name="default-route"> <properties> <help>Default EVPN type-5 route</help> <valueless/> </properties> </leafNode> #include <include/bgp/route-distinguisher.xml.i> <leafNode name="route-type"> <properties> <help>Match route-type</help> <completionHelp> <list>macip multicast prefix</list> </completionHelp> <valueHelp> <format>macip</format> <description>mac-ip route</description> </valueHelp> <valueHelp> <format>multicast</format> <description>IMET route</description> </valueHelp> <valueHelp> <format>prefix</format> <description>Prefix route</description> </valueHelp> <constraint> <regex>(macip|multicast|prefix)</regex> </constraint> </properties> </leafNode> #include <include/vni.xml.i> </children> </node> <leafNode name="extcommunity"> <properties> <help>BGP extended community to match</help> <completionHelp> <path>policy extcommunity-list</path> </completionHelp> </properties> </leafNode> #include <include/generic-interface.xml.i> <node name="ip"> <properties> <help>IP prefix parameters to match</help> </properties> <children> <node name="address"> <properties> <help>IP address of route to match</help> </properties> <children> <leafNode name="access-list"> <properties> <help>IP access-list to match</help> <valueHelp> <format>u32:1-99</format> <description>IP standard access list</description> </valueHelp> <valueHelp> <format>u32:100-199</format> <description>IP extended access list</description> </valueHelp> <valueHelp> <format>u32:1300-1999</format> <description>IP standard access list (expanded range)</description> </valueHelp> <valueHelp> <format>u32:2000-2699</format> <description>IP extended access list (expanded range)</description> </valueHelp> </properties> </leafNode> <leafNode name="prefix-list"> <properties> <help>IP prefix-list to match</help> <completionHelp> <path>policy prefix-list</path> </completionHelp> </properties> </leafNode> <leafNode name="prefix-len"> <properties> <help>IP prefix-length to match (can be used for kernel routes only)</help> <valueHelp> <format>u32:0-32</format> <description>Prefix length</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-32"/> </constraint> </properties> </leafNode> </children> </node> <!-- T3304 but it overwrite node nexthop <leafNode name="nexthop"> <properties> <help>IP next-hop of route to match</help> <valueHelp> <format>ipv4</format> <description>Next-hop IPv4 router address</description> </valueHelp> <constraint> <validator name="ipv4-address"/> </constraint> </properties> </leafNode> --> <node name="nexthop"> <properties> <help>IP next-hop of route to match</help> </properties> <children> <leafNode name="address"> <properties> <help>IP address to match</help> <valueHelp> <format>ipv4</format> <description>Nexthop IP address</description> </valueHelp> <constraint> <validator name="ipv4-address"/> </constraint> </properties> </leafNode> <leafNode name="access-list"> <properties> <help>IP access-list to match</help> <valueHelp> <format>u32:1-99</format> <description>IP standard access list</description> </valueHelp> <valueHelp> <format>u32:100-199</format> <description>IP extended access list</description> </valueHelp> <valueHelp> <format>u32:1300-1999</format> <description>IP standard access list (expanded range)</description> </valueHelp> <valueHelp> <format>u32:2000-2699</format> <description>IP extended access list (expanded range)</description> </valueHelp> </properties> </leafNode> <leafNode name="prefix-len"> <properties> <help>IP prefix-length to match</help> <valueHelp> <format>u32:0-32</format> <description>Prefix length</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-32"/> </constraint> </properties> </leafNode> <leafNode name="prefix-list"> <properties> <help>IP prefix-list to match</help> <completionHelp> <path>policy prefix-list</path> </completionHelp> </properties> </leafNode> <leafNode name="type"> <properties> <help>Match type</help> <completionHelp> <list>blackhole</list> </completionHelp> <valueHelp> <format>blackhole</format> <description>Blackhole</description> </valueHelp> <constraint> <regex>(blackhole)</regex> </constraint> </properties> </leafNode> </children> </node> <node name="route-source"> <properties> <help>Match advertising source address of route</help> </properties> <children> <leafNode name="access-list"> <properties> <help>IP access-list to match</help> <valueHelp> <format>u32:1-99</format> <description>IP standard access list</description> </valueHelp> <valueHelp> <format>u32:100-199</format> <description>IP extended access list</description> </valueHelp> <valueHelp> <format>u32:1300-1999</format> <description>IP standard access list (expanded range)</description> </valueHelp> <valueHelp> <format>u32:2000-2699</format> <description>IP extended access list (expanded range)</description> </valueHelp> </properties> </leafNode> <leafNode name="prefix-list"> <properties> <help>IP prefix-list to match</help> <completionHelp> <path>policy prefix-list</path> </completionHelp> </properties> </leafNode> </children> </node> </children> </node> <node name="ipv6"> <properties> <help>IPv6 prefix parameters to match</help> </properties> <children> <node name="address"> <properties> <help>IPv6 address of route to match</help> </properties> <children> <leafNode name="access-list"> <properties> <help>IPv6 access-list to match</help> <valueHelp> <format>txt</format> <description>IPV6 access list name</description> </valueHelp> <completionHelp> <path>policy access-list6</path> </completionHelp> </properties> </leafNode> <leafNode name="prefix-list"> <properties> <help>IPv6 prefix-list to match</help> <completionHelp> <path>policy prefix-list6</path> </completionHelp> </properties> </leafNode> <leafNode name="prefix-len"> <properties> <help>IPv6 prefix-length to match (can be used for kernel routes only)</help> <valueHelp> <format>u32:0-128</format> <description>Prefix length</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-128"/> </constraint> </properties> </leafNode> </children> </node> <!-- T3976 but it overwrite node nexthop <leafNode name="nexthop"> <properties> <help>IPv6 next-hop of route to match</help> <valueHelp> <format>ipv6</format> <description>Nexthop IPv6 address</description> </valueHelp> <constraint> <validator name="ipv6-address"/> </constraint> </properties> </leafNode> </children> </node> --> <node name="nexthop"> <properties> <help>IPv6 next-hop of route to match</help> </properties> <children> <leafNode name="address"> <properties> <help>IPv6 address of next-hop</help> <valueHelp> <format>ipv6</format> <description>Nexthop IPv6 address</description> </valueHelp> <constraint> <validator name="ipv6-address"/> </constraint> </properties> </leafNode> <leafNode name="access-list"> <properties> <help>IPv6 access-list to match</help> <valueHelp> <format>txt</format> <description>IPV6 access list name</description> </valueHelp> <completionHelp> <path>policy access-list6</path> </completionHelp> </properties> </leafNode> <leafNode name="prefix-list"> <properties> <help>IPv6 prefix-list to match</help> <completionHelp> <path>policy prefix-list6</path> </completionHelp> </properties> </leafNode> <leafNode name="type"> <properties> <help>Match type</help> <completionHelp> <list>blackhole</list> </completionHelp> <valueHelp> <format>blackhole</format> <description>Blackhole</description> </valueHelp> <constraint> <regex>(blackhole)</regex> </constraint> </properties> </leafNode> </children> </node> </children> </node> <node name="large-community"> <properties> <help>Match BGP large communities</help> </properties> <children> <leafNode name="large-community-list"> <properties> <help>BGP large-community-list to match</help> <completionHelp> <path>policy large-community-list</path> </completionHelp> </properties> </leafNode> </children> </node> <leafNode name="local-preference"> <properties> <help>Local Preference</help> <valueHelp> <format>u32:0-4294967295</format> <description>Local Preference</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4294967295"/> </constraint> </properties> </leafNode> <leafNode name="metric"> <properties> <help>Metric of route to match</help> <valueHelp> <format>u32:1-65535</format> <description>Route metric</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> </leafNode> <leafNode name="origin"> <properties> <help>BGP origin code to match</help> <completionHelp> <list>egp igp incomplete</list> </completionHelp> <valueHelp> <format>egp</format> <description>Exterior gateway protocol origin</description> </valueHelp> <valueHelp> <format>igp</format> <description>Interior gateway protocol origin</description> </valueHelp> <valueHelp> <format>incomplete</format> <description>Incomplete origin</description> </valueHelp> <constraint> <regex>(egp|igp|incomplete)</regex> </constraint> </properties> </leafNode> <leafNode name="peer"> <properties> <help>Peer address to match</help> <valueHelp> <format>ipv4</format> <description>Peer IP address</description> </valueHelp> <valueHelp> <format>ipv6</format> <description>Peer IPv6 address</description> </valueHelp> <constraint> <validator name="ip-address"/> </constraint> </properties> </leafNode> <leafNode name="protocol"> <properties> <help>Match protocol via which the route was learnt</help> <completionHelp> <list>babel bgp connected isis kernel ospf ospfv3 rip ripng static table vnc</list> </completionHelp> <valueHelp> <format>babel</format> <description>Babel routing protocol (Babel)</description> </valueHelp> <valueHelp> <format>bgp</format> <description>Border Gateway Protocol (BGP)</description> </valueHelp> <valueHelp> <format>connected</format> <description>Connected routes (directly attached subnet or host)</description> </valueHelp> <valueHelp> <format>isis</format> <description>Intermediate System to Intermediate System (IS-IS)</description> </valueHelp> <valueHelp> <format>kernel</format> <description>Kernel routes</description> </valueHelp> <valueHelp> <format>ospf</format> <description>Open Shortest Path First (OSPFv2)</description> </valueHelp> <valueHelp> <format>ospfv3</format> <description>Open Shortest Path First (IPv6) (OSPFv3)</description> </valueHelp> <valueHelp> <format>rip</format> <description>Routing Information Protocol (RIP)</description> </valueHelp> <valueHelp> <format>ripng</format> <description>Routing Information Protocol next-generation (IPv6) (RIPng)</description> </valueHelp> <valueHelp> <format>static</format> <description>Statically configured routes</description> </valueHelp> <valueHelp> <format>table</format> <description>Non-main Kernel Routing Table</description> </valueHelp> <valueHelp> <format>vnc</format> <description>Virtual Network Control (VNC)</description> </valueHelp> <constraint> <regex>(babel|bgp|connected|isis|kernel|ospf|ospfv3|rip|ripng|static|table|vnc)</regex> </constraint> </properties> </leafNode> <leafNode name="rpki"> <properties> <help>Match RPKI validation result</help> <completionHelp> <list>invalid notfound valid</list> </completionHelp> <valueHelp> <format>invalid</format> <description>Match invalid entries</description> </valueHelp> <valueHelp> <format>notfound</format> <description>Match notfound entries</description> </valueHelp> <valueHelp> <format>valid</format> <description>Match valid entries</description> </valueHelp> <constraint> <regex>(invalid|notfound|valid)</regex> </constraint> </properties> </leafNode> + <leafNode name="source-vrf"> + <properties> + <help>Source vrf</help> + #include <include/constraint/vrf.xml.i> + <valueHelp> + <format>txt</format> + <description>VRF instance name</description> + </valueHelp> + <completionHelp> + <path>vrf name</path> + <list>default</list> + </completionHelp> + </properties> + </leafNode> #include <include/policy/tag.xml.i> </children> </node> <node name="on-match"> <properties> <help>Exit policy on matches</help> </properties> <children> <leafNode name="goto"> <properties> <help>Rule number to goto on match</help> <valueHelp> <format>u32:1-65535</format> <description>Rule number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> </leafNode> <leafNode name="next"> <properties> <help>Next sequence number to goto on match</help> <valueless/> </properties> </leafNode> </children> </node> <node name="set"> <properties> <help>Route parameters</help> </properties> <children> <node name="aggregator"> <properties> <help>BGP aggregator attribute</help> </properties> <children> <leafNode name="as"> <properties> <help>AS number of an aggregation</help> <valueHelp> <format>u32:1-4294967295</format> <description>Rule number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-4294967295"/> </constraint> </properties> </leafNode> <leafNode name="ip"> <properties> <help>IP address of an aggregation</help> <valueHelp> <format>ipv4</format> <description>IP address</description> </valueHelp> <constraint> <validator name="ipv4-address"/> </constraint> </properties> </leafNode> </children> </node> <node name="as-path"> <properties> <help>Transform BGP AS_PATH attribute</help> </properties> <children> <leafNode name="exclude"> <properties> <help>Remove/exclude from the as-path attribute</help> <completionHelp> <list>all</list> </completionHelp> <valueHelp> <format>u32:1-4294967295</format> <description>AS number</description> </valueHelp> <valueHelp> <format>all</format> <description>Exclude all AS numbers from the as-path</description> </valueHelp> <constraint> <validator name="as-number-list"/> <regex>(all)</regex> </constraint> </properties> </leafNode> <leafNode name="prepend"> <properties> <help>Prepend to the as-path</help> <valueHelp> <format>u32:1-4294967295</format> <description>AS number</description> </valueHelp> <constraint> <validator name="as-number-list"/> </constraint> </properties> </leafNode> <leafNode name="prepend-last-as"> <properties> <help>Use the last AS-number in the as-path</help> <valueHelp> <format>u32:1-10</format> <description>Number of times to insert</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-10"/> </constraint> </properties> </leafNode> </children> </node> <leafNode name="atomic-aggregate"> <properties> <help>BGP atomic aggregate attribute</help> <valueless/> </properties> </leafNode> <node name="community"> <properties> <help>BGP community attribute</help> </properties> <children> <leafNode name="add"> <properties> <help>Add communities to a prefix</help> #include <include/policy/community-value-list.xml.i> </properties> </leafNode> <leafNode name="replace"> <properties> <help>Set communities for a prefix</help> #include <include/policy/community-value-list.xml.i> </properties> </leafNode> #include <include/policy/community-clear.xml.i> <leafNode name="delete"> <properties> <help>Remove communities defined in a list from a prefix</help> <completionHelp> <path>policy community-list</path> </completionHelp> <valueHelp> <description>Community-list</description> <format>txt</format> </valueHelp> </properties> </leafNode> </children> </node> <node name="large-community"> <properties> <help>BGP large community attribute</help> </properties> <children> <leafNode name="add"> <properties> <help>Add large communities to a prefix ;</help> #include <include/policy/large-community-value-list.xml.i> </properties> </leafNode> <leafNode name="replace"> <properties> <help>Set large communities for a prefix</help> #include <include/policy/large-community-value-list.xml.i> </properties> </leafNode> #include <include/policy/community-clear.xml.i> <leafNode name="delete"> <properties> <help>Remove communities defined in a list from a prefix</help> <completionHelp> <path>policy large-community-list</path> </completionHelp> <valueHelp> <description>Community-list</description> <format>txt</format> </valueHelp> </properties> </leafNode> </children> </node> <node name="extcommunity"> <properties> <help>BGP extended community attribute</help> </properties> <children> <leafNode name="bandwidth"> <properties> <help>Bandwidth value in Mbps</help> <completionHelp> <list>cumulative num-multipaths</list> </completionHelp> <valueHelp> <format>u32:1-25600</format> <description>Bandwidth value in Mbps</description> </valueHelp> <valueHelp> <format>cumulative</format> <description>Cumulative bandwidth of all multipaths (outbound-only)</description> </valueHelp> <valueHelp> <format>num-multipaths</format> <description>Internally computed bandwidth based on number of multipaths (outbound-only)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-25600"/> <regex>(cumulative|num-multipaths)</regex> </constraint> </properties> </leafNode> <leafNode name="bandwidth-non-transitive"> <properties> <help>The link bandwidth extended community is encoded as non-transitive</help> <valueless/> </properties> </leafNode> <leafNode name="rt"> <properties> <help>Set route target value</help> #include <include/policy/extended-community-value-list.xml.i> </properties> </leafNode> <leafNode name="soo"> <properties> <help>Set Site of Origin value</help> #include <include/policy/extended-community-value-list.xml.i> </properties> </leafNode> #include <include/policy/community-clear.xml.i> </children> </node> <leafNode name="distance"> <properties> <help>Locally significant administrative distance</help> <valueHelp> <format>u32:0-255</format> <description>Distance value</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-255"/> </constraint> </properties> </leafNode> <node name="evpn"> <properties> <help>Ethernet Virtual Private Network</help> </properties> <children> <node name="gateway"> <properties> <help>Set gateway IP for prefix advertisement route</help> </properties> <children> <leafNode name="ipv4"> <properties> <help>Set gateway IPv4 address</help> <valueHelp> <format>ipv4</format> <description>Gateway IPv4 address</description> </valueHelp> <constraint> <validator name="ipv4-address"/> </constraint> </properties> </leafNode> <leafNode name="ipv6"> <properties> <help>Set gateway IPv6 address</help> <valueHelp> <format>ipv6</format> <description>Gateway IPv6 address</description> </valueHelp> <constraint> <validator name="ipv6-address"/> </constraint> </properties> </leafNode> </children> </node> </children> </node> <leafNode name="ip-next-hop"> <properties> <help>Nexthop IP address</help> <completionHelp> <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script> <list>unchanged peer-address</list> </completionHelp> <valueHelp> <format>ipv4</format> <description>IP address</description> </valueHelp> <valueHelp> <format>unchanged</format> <description>Set the BGP nexthop address as unchanged</description> </valueHelp> <valueHelp> <format>peer-address</format> <description>Set the BGP nexthop address to the address of the peer</description> </valueHelp> <constraint> <validator name="ipv4-address"/> <regex>(unchanged|peer-address)</regex> </constraint> </properties> </leafNode> <node name="ipv6-next-hop"> <properties> <help>Nexthop IPv6 address</help> </properties> <children> <leafNode name="global"> <properties> <help>Nexthop IPv6 global address</help> <completionHelp> <script>${vyos_completion_dir}/list_local_ips.sh --ipv6</script> </completionHelp> <valueHelp> <format>ipv6</format> <description>IPv6 address and prefix length</description> </valueHelp> <constraint> <validator name="ipv6-address"/> </constraint> </properties> </leafNode> <leafNode name="local"> <properties> <help>Nexthop IPv6 local address</help> <completionHelp> <script>${vyos_completion_dir}/list_local_ips.sh --ipv6</script> </completionHelp> <valueHelp> <format>ipv6</format> <description>IPv6 address and prefix length</description> </valueHelp> <constraint> <validator name="ipv6-address"/> </constraint> </properties> </leafNode> <leafNode name="peer-address"> <properties> <help>Use peer address (for BGP only)</help> <valueless/> </properties> </leafNode> <leafNode name="prefer-global"> <properties> <help>Prefer global address as the nexthop</help> <valueless/> </properties> </leafNode> </children> </node> <node name="l3vpn-nexthop"> <properties> <help>Next hop Information</help> </properties> <children> <node name="encapsulation"> <properties> <help>Encapsulation options (for BGP only)</help> </properties> <children> <leafNode name="gre"> <properties> <help>Accept L3VPN traffic over GRE encapsulation</help> <valueless/> </properties> </leafNode> </children> </node> </children> </node> <leafNode name="local-preference"> <properties> <help>BGP local preference attribute</help> <valueHelp> <format>u32:0-4294967295</format> <description>Local preference value</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4294967295"/> </constraint> </properties> </leafNode> <leafNode name="metric"> <properties> <help>Destination routing protocol metric</help> <valueHelp> <format><+/-metric></format> <description>Add or subtract metric</description> </valueHelp> <valueHelp> <format>u32:0-4294967295</format> <description>Metric value</description> </valueHelp> <valueHelp> <format><+/-rtt></format> <description>Add or subtract round trip time</description> </valueHelp> <valueHelp> <format><rtt></format> <description>Round trip time</description> </valueHelp> <constraint> <validator name="numeric" argument="--relative --"/> <validator name="numeric" argument="--range 0-4294967295"/> <regex>^[+|-]?rtt$</regex> </constraint> </properties> </leafNode> <leafNode name="metric-type"> <properties> <help>Open Shortest Path First (OSPF) external metric-type</help> <completionHelp> <list>type-1 type-2</list> </completionHelp> <valueHelp> <format>type-1</format> <description>OSPF external type 1 metric</description> </valueHelp> <valueHelp> <format>type-2</format> <description>OSPF external type 2 metric</description> </valueHelp> <constraint> <regex>(type-1|type-2)</regex> </constraint> </properties> </leafNode> <leafNode name="origin"> <properties> <help>Border Gateway Protocl (BGP) origin code</help> <completionHelp> <list>igp egp incomplete</list> </completionHelp> <valueHelp> <format>igp</format> <description>Interior gateway protocol origin</description> </valueHelp> <valueHelp> <format>egp</format> <description>Exterior gateway protocol origin</description> </valueHelp> <valueHelp> <format>incomplete</format> <description>Incomplete origin</description> </valueHelp> <constraint> <regex>(igp|egp|incomplete)</regex> </constraint> </properties> </leafNode> <leafNode name="originator-id"> <properties> <help>BGP originator ID attribute</help> <valueHelp> <format>ipv4</format> <description>Orignator IP address</description> </valueHelp> <constraint> <validator name="ipv4-address"/> </constraint> </properties> </leafNode> <leafNode name="src"> <properties> <help>Source address for route</help> <completionHelp> <script>${vyos_completion_dir}/list_local_ips.sh --both</script> </completionHelp> <valueHelp> <format>ipv4</format> <description>IPv4 address</description> </valueHelp> <valueHelp> <format>ipv6</format> <description>IPv6 address</description> </valueHelp> <constraint> <validator name="ip-address"/> </constraint> </properties> </leafNode> <leafNode name="table"> <properties> <help>Set prefixes to table</help> <valueHelp> <format>u32:1-4294967295</format> <description>Table value</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-4294967295"/> </constraint> </properties> </leafNode> #include <include/policy/tag.xml.i> <leafNode name="weight"> <properties> <help>BGP weight attribute</help> <valueHelp> <format>u32:0-4294967295</format> <description>BGP weight</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4294967295"/> </constraint> </properties> </leafNode> </children> </node> </children> </tagNode> </children> </tagNode> </children> </node> </interfaceDefinition> diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 9d4fc0845..985097726 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1,2006 +1,2021 @@ #!/usr/bin/env python3 # # Copyright (C) 2021-2025 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 base_vyostest_shim import CSTORE_GUARD_TIME from vyos.configsession import ConfigSessionError from vyos.utils.process import cmd base_path = ['policy'] class TestPolicy(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(TestPolicy, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) cls.cli_delete(cls, ['vrf']) # Enable CSTORE guard time required by FRR related tests cls._commit_guard_time = CSTORE_GUARD_TIME 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' : { '5' : { 'action' : 'permit', 'source' : { 'any' : '' }, 'destination' : { 'host' : '2.2.2.2' }, }, '10' : { 'action' : 'deny', 'source' : { 'any' : '' }, 'destination' : { 'any' : '' }, }, }, }, '2000' : { 'rule' : { '5' : { 'action' : 'permit', 'destination' : { 'any' : '' }, 'source' : { 'network' : '10.0.0.0', 'inverse-mask' : '0.255.255.255' }, }, '10' : { 'action' : 'permit', 'destination' : { 'any' : '' }, 'source' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, }, '15' : { 'action' : 'permit', 'destination' : { 'any' : '' }, 'source' : { 'network' : '192.168.0.0', 'inverse-mask' : '0.0.255.255' }, }, '20' : { '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' }, }, '25' : { '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' }, }, '30' : { '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(): for rule, rule_config in acl_config['rule'].items(): tmp = f'access-list {acl} seq {rule}' 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]: # XXX: Some weird side rule from the old vyatta days # possible to clean this up after the vyos-1x migration if int(acl) in range(100, 200) or int(acl) in range(2000, 2700): tmp += ' host' 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) def test_access_list6(self): acls = { '50' : { 'rule' : { '5' : { 'action' : 'permit', 'source' : { 'any' : '' }, }, '10' : { 'action' : 'deny', 'source' : { 'network' : '2001:db8:10::/48', 'exact-match' : '' }, }, '15' : { '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' : '' }, }, '25' : { '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(): for rule, rule_config in acl_config['rule'].items(): tmp = f'ipv6 access-list {acl} seq {rule}' 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) def test_as_path_list(self): test_data = { 'VyOS' : { 'rule' : { '5' : { 'action' : 'permit', 'regex' : '^44501 64502$', }, '10' : { 'action' : 'permit', 'regex' : '44501|44502|44503', }, '15' : { 'action' : 'permit', 'regex' : '^44501_([0-9]+_)+', }, }, }, 'Customers' : { 'rule' : { '5' : { 'action' : 'permit', 'regex' : '_10_', }, '10' : { 'action' : 'permit', 'regex' : '_20_', }, '15' : { 'action' : 'permit', 'regex' : '_30_', }, '20' : { 'action' : 'deny', 'regex' : '_40_', }, }, }, 'bogons' : { 'rule' : { '5' : { 'action' : 'permit', 'regex' : '_0_', }, '10' : { 'action' : 'permit', 'regex' : '_23456_', }, '15' : { 'action' : 'permit', 'regex' : '_6449[6-9]_|_65[0-4][0-9][0-9]_|_655[0-4][0-9]_|_6555[0-1]_', }, '20' : { '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} seq {rule}' 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' : { '5' : { 'action' : 'permit', 'regex' : '.*', }, }, }, '200' : { 'rule' : { '5' : { 'action' : 'deny', 'regex' : '^1:201$', }, '10' : { 'action' : 'deny', 'regex' : '1:101$', }, '15' : { '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' : { '5' : { 'action' : 'permit', 'regex' : '.*', }, }, }, '200' : { 'rule' : { '5' : { 'action' : 'deny', 'regex' : '^1:201$', }, '10' : { 'action' : 'deny', 'regex' : '1:101$', }, '15' : { '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' : { '5' : { 'action' : 'permit', 'regex' : '667:123:100', }, }, }, 'bar' : { 'rule' : { '5' : { 'action' : 'permit', 'regex' : '65000:120:10', }, '10' : { 'action' : 'permit', 'regex' : '65000:120:20', }, '15' : { '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' : { '5' : { 'action' : 'permit', 'prefix' : '10.0.0.0/8', 'ge' : '16', 'le' : '24', }, '10' : { 'action' : 'deny', 'prefix' : '172.16.0.0/12', 'ge' : '16', }, '15' : { 'action' : 'permit', 'prefix' : '192.168.0.0/16', }, }, }, 'bar' : { 'rule' : { '5' : { 'action' : 'permit', 'prefix' : '10.0.10.0/24', 'ge' : '25', 'le' : '26', }, '10' : { 'action' : 'deny', 'prefix' : '10.0.20.0/24', 'le' : '25', }, '15' : { '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' : { '5' : { 'action' : 'permit', 'prefix' : '2001:db8::/32', 'ge' : '40', 'le' : '48', }, '10' : { 'action' : 'deny', 'prefix' : '2001:db8::/32', 'ge' : '48', }, '15' : { 'action' : 'permit', 'prefix' : '2001:db8:1000::/64', }, }, }, 'bar' : { 'rule' : { '5' : { 'action' : 'permit', 'prefix' : '2001:db8:100::/40', 'ge' : '48', }, '10' : { 'action' : 'permit', 'prefix' : '2001:db8:200::/40', 'ge' : '48', }, '15' : { '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) def test_prefix_list_duplicates(self): # FRR does not allow to specify the same profix list rule multiple times # # vyos(config)# ip prefix-list foo seq 10 permit 192.0.2.0/24 # vyos(config)# ip prefix-list foo seq 20 permit 192.0.2.0/24 # % Configuration failed. # Error type: validation # Error description: duplicated prefix list value: 192.0.2.0/24 # There is also a VyOS verify() function to test this prefix = '100.64.0.0/10' prefix_list = 'duplicates' test_range = range(20, 25) path = base_path + ['prefix-list', prefix_list] for rule in test_range: self.cli_set(path + ['rule', str(rule), 'action', 'permit']) self.cli_set(path + ['rule', str(rule), 'prefix', prefix]) # Duplicate prefixes with self.assertRaises(ConfigSessionError): self.cli_commit() for rule in test_range: self.cli_set(path + ['rule', str(rule), 'le', str(rule)]) self.cli_commit() config = self.getFRRconfig('ip prefix-list', end='') for rule in test_range: tmp = f'ip prefix-list {prefix_list} seq {rule} permit {prefix} le {rule}' self.assertIn(tmp, config) def test_route_map_community_set(self): test_data = { "community-configuration": { "rule": { "10": { "action": "permit", "set": { "community": { "replace": [ "65000:10", "65001:11" ] }, "extcommunity": { "bandwidth": "200", "rt": [ "65000:10", "192.168.0.1:11" ], "soo": [ "192.168.0.1:11", "65000:10" ] }, "large-community": { "replace": [ "65000:65000:10", "65000:65000:11" ] } } }, "20": { "action": "permit", "set": { "community": { "add": [ "65000:10", "65001:11" ] }, "extcommunity": { "bandwidth": "200", "bandwidth-non-transitive": {} }, "large-community": { "add": [ "65000:65000:10", "65000:65000:11" ] } } }, "30": { "action": "permit", "set": { "community": { "none": {} }, "extcommunity": { "none": {} }, "large-community": { "none": {} } } } } } } for route_map, route_map_config in test_data.items(): path = base_path + ['route-map', route_map] self.cli_set(path + ['description', f'VyOS ROUTE-MAP {route_map}']) if 'rule' not in route_map_config: continue for rule, rule_config in route_map_config['rule'].items(): if 'action' in rule_config: self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) if 'set' in rule_config: #Add community in configuration if 'community' in rule_config['set']: if 'none' in rule_config['set']['community']: self.cli_set(path + ['rule', rule, 'set', 'community', 'none']) else: community_path = path + ['rule', rule, 'set', 'community'] if 'add' in rule_config['set']['community']: for community_unit in rule_config['set']['community']['add']: self.cli_set(community_path + ['add', community_unit]) if 'replace' in rule_config['set']['community']: for community_unit in rule_config['set']['community']['replace']: self.cli_set(community_path + ['replace', community_unit]) #Add large-community in configuration if 'large-community' in rule_config['set']: if 'none' in rule_config['set']['large-community']: self.cli_set(path + ['rule', rule, 'set', 'large-community', 'none']) else: community_path = path + ['rule', rule, 'set', 'large-community'] if 'add' in rule_config['set']['large-community']: for community_unit in rule_config['set']['large-community']['add']: self.cli_set(community_path + ['add', community_unit]) if 'replace' in rule_config['set']['large-community']: for community_unit in rule_config['set']['large-community']['replace']: self.cli_set(community_path + ['replace', community_unit]) #Add extcommunity in configuration if 'extcommunity' in rule_config['set']: if 'none' in rule_config['set']['extcommunity']: self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'none']) else: if 'bandwidth' in rule_config['set']['extcommunity']: self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'bandwidth', rule_config['set']['extcommunity']['bandwidth']]) if 'bandwidth-non-transitive' in rule_config['set']['extcommunity']: self.cli_set(path + ['rule', rule, 'set','extcommunity', 'bandwidth-non-transitive']) if 'rt' in rule_config['set']['extcommunity']: for community_unit in rule_config['set']['extcommunity']['rt']: self.cli_set(path + ['rule', rule, 'set', 'extcommunity','rt',community_unit]) if 'soo' in rule_config['set']['extcommunity']: for community_unit in rule_config['set']['extcommunity']['soo']: self.cli_set(path + ['rule', rule, 'set', 'extcommunity','soo',community_unit]) self.cli_commit() for route_map, route_map_config in test_data.items(): if 'rule' not in route_map_config: continue for rule, rule_config in route_map_config['rule'].items(): name = f'route-map {route_map} {rule_config["action"]} {rule}' config = self.getFRRconfig(name) self.assertIn(name, config) if 'set' in rule_config: #Check community if 'community' in rule_config['set']: if 'none' in rule_config['set']['community']: tmp = f'set community none' self.assertIn(tmp, config) if 'replace' in rule_config['set']['community']: values = ' '.join(rule_config['set']['community']['replace']) tmp = f'set community {values}' self.assertIn(tmp, config) if 'add' in rule_config['set']['community']: values = ' '.join(rule_config['set']['community']['add']) tmp = f'set community {values} additive' self.assertIn(tmp, config) #Check large-community if 'large-community' in rule_config['set']: if 'none' in rule_config['set']['large-community']: tmp = f'set large-community none' self.assertIn(tmp, config) if 'replace' in rule_config['set']['large-community']: values = ' '.join(rule_config['set']['large-community']['replace']) tmp = f'set large-community {values}' self.assertIn(tmp, config) if 'add' in rule_config['set']['large-community']: values = ' '.join(rule_config['set']['large-community']['add']) tmp = f'set large-community {values} additive' self.assertIn(tmp, config) #Check extcommunity if 'extcommunity' in rule_config['set']: if 'none' in rule_config['set']['extcommunity']: tmp = 'set extcommunity none' self.assertIn(tmp, config) if 'bandwidth' in rule_config['set']['extcommunity']: values = rule_config['set']['extcommunity']['bandwidth'] tmp = f'set extcommunity bandwidth {values}' if 'bandwidth-non-transitive' in rule_config['set']['extcommunity']: tmp = tmp + ' non-transitive' self.assertIn(tmp, config) if 'rt' in rule_config['set']['extcommunity']: values = ' '.join(rule_config['set']['extcommunity']['rt']) tmp = f'set extcommunity rt {values}' self.assertIn(tmp, config) if 'soo' in rule_config['set']['extcommunity']: values = ' '.join(rule_config['set']['extcommunity']['soo']) tmp = f'set extcommunity soo {values}' self.assertIn(tmp, config) def test_route_map(self): access_list = '50' as_path_list = '100' test_interface = 'eth0' community_list = 'BGP-comm-0815' # ext community name only allows alphanumeric characters and no hyphen :/ # maybe change this if possible in vyos-1x rewrite extcommunity_list = 'BGPextcomm123' large_community_list = 'bgp-large-community-123456' prefix_list = 'foo-pfx-list' ipv6_nexthop_address = 'fe80::1' local_pref = '300' metric = '50' peer = '2.3.4.5' peerv6 = '2001:db8::1' tag = '6542' goto = '25' ipv4_nexthop_address= '192.0.2.2' ipv4_prefix_len= '18' ipv6_prefix_len= '122' ipv4_nexthop_type= 'blackhole' ipv6_nexthop_type= 'blackhole' test_data = { 'foo-map-bar' : { 'rule' : { '5' : { 'action' : 'permit', 'continue' : '20', }, '10' : { 'action' : 'permit', 'call' : 'complicated-configuration', }, }, }, 'a-matching-rule-0815': { 'rule' : { '5' : { 'action' : 'deny', 'match' : { 'as-path' : as_path_list, 'rpki-invalid': '', 'tag': tag, }, }, '10' : { 'action' : 'permit', 'match' : { 'community' : community_list, 'interface' : test_interface, 'rpki-not-found': '', }, }, '15' : { 'action' : 'permit', 'match' : { 'extcommunity' : extcommunity_list, 'rpki-valid': '', }, 'on-match' : { 'next' : '', }, }, '20' : { 'action' : 'permit', 'match' : { 'ip-address-acl': access_list, 'ip-nexthop-acl': access_list, 'ip-route-source-acl': access_list, 'ipv6-address-acl': access_list, 'origin-incomplete' : '', }, 'on-match' : { 'goto' : goto, }, }, '25' : { 'action' : 'permit', 'match' : { 'ip-address-pfx': prefix_list, 'ip-nexthop-pfx': prefix_list, 'ip-route-source-pfx': prefix_list, 'ipv6-address-pfx': prefix_list, 'origin-igp': '', }, }, '30' : { 'action' : 'permit', 'match' : { 'ipv6-nexthop-address' : ipv6_nexthop_address, 'ipv6-nexthop-access-list' : access_list, 'ipv6-nexthop-prefix-list' : prefix_list, 'ipv6-nexthop-type' : ipv6_nexthop_type, 'ipv6-address-pfx-len' : ipv6_prefix_len, 'large-community' : large_community_list, 'local-pref' : local_pref, 'metric': metric, 'origin-egp': '', 'peer' : peer, }, }, '31' : { 'action' : 'permit', 'match' : { 'peer' : peerv6, }, }, '40' : { 'action' : 'permit', 'match' : { 'ip-nexthop-addr' : ipv4_nexthop_address, 'ip-address-pfx-len' : ipv4_prefix_len, }, }, '42' : { 'action' : 'deny', 'match' : { 'ip-nexthop-plen' : ipv4_prefix_len, }, }, '44' : { 'action' : 'permit', 'match' : { 'ip-nexthop-type' : ipv4_nexthop_type, }, }, }, }, 'complicated-configuration' : { 'rule' : { '10' : { 'action' : 'deny', 'set' : { 'aggregator-as' : '1234567890', 'aggregator-ip' : '10.255.255.0', 'as-path-exclude' : '1234', 'as-path-prepend' : '1234567890 987654321', 'as-path-prepend-last-as' : '5', 'atomic-aggregate' : '', 'distance' : '110', 'ipv6-next-hop-global' : '2001::1', 'ipv6-next-hop-local' : 'fe80::1', 'ip-next-hop' : '192.168.1.1', 'local-preference' : '500', 'metric' : '150', 'metric-type' : 'type-1', 'origin' : 'incomplete', 'l3vpn' : '', 'originator-id' : '172.16.10.1', 'src' : '100.0.0.1', 'tag' : '65530', 'weight' : '2', }, }, }, }, 'bandwidth-configuration' : { 'rule' : { '10' : { 'action' : 'deny', 'set' : { 'as-path-prepend' : '100 100', 'distance' : '200', 'extcommunity-bw' : 'num-multipaths', }, }, }, }, 'evpn-configuration' : { 'rule' : { '10' : { 'action' : 'permit', 'match' : { 'evpn-default-route' : '', 'evpn-rd' : '100:300', 'evpn-route-type' : 'prefix', 'evpn-vni' : '1234', }, }, '20' : { 'action' : 'permit', 'set' : { 'as-path-exclude' : 'all', 'evpn-gateway-ipv4' : '192.0.2.99', 'evpn-gateway-ipv6' : '2001:db8:f00::1', }, }, }, }, 'match-protocol' : { 'rule' : { '10' : { 'action' : 'permit', 'match' : { 'protocol' : 'static', }, }, '20' : { 'action' : 'permit', 'match' : { 'protocol' : 'bgp', }, }, }, }, 'relative-metric' : { 'rule' : { '10' : { 'action' : 'permit', 'match' : { 'ip-nexthop-addr' : ipv4_nexthop_address, }, 'set' : { 'metric' : '+10', }, }, '20' : { 'action' : 'permit', 'match' : { 'ip-nexthop-addr' : ipv4_nexthop_address, }, 'set' : { 'metric' : '-20', }, }, '30': { 'action': 'permit', 'match': { 'ip-nexthop-addr': ipv4_nexthop_address, }, 'set': { 'metric': 'rtt', }, }, '40': { 'action': 'permit', 'match': { 'ip-nexthop-addr': ipv4_nexthop_address, }, 'set': { 'metric': '+rtt', }, }, '50': { 'action': 'permit', 'match': { 'ip-nexthop-addr': ipv4_nexthop_address, }, 'set': { 'metric': '-rtt', }, }, }, }, + 'vrf-match': { + 'rule': { + '10': { + 'action': 'permit', + 'match': { + 'source-vrf': 'TEST', + }, + }, + }, + }, } self.cli_set(['policy', 'access-list', access_list, 'rule', '10', 'action', 'permit']) self.cli_set(['policy', 'access-list', access_list, 'rule', '10', 'source', 'host', '1.1.1.1']) self.cli_set(['policy', 'access-list6', access_list, 'rule', '10', 'action', 'permit']) self.cli_set(['policy', 'access-list6', access_list, 'rule', '10', 'source', 'network', '2001:db8::/32']) self.cli_set(['policy', 'as-path-list', as_path_list, 'rule', '10', 'action', 'permit']) self.cli_set(['policy', 'as-path-list', as_path_list, 'rule', '10', 'regex', '64501 64502']) self.cli_set(['policy', 'community-list', community_list, 'rule', '10', 'action', 'deny']) self.cli_set(['policy', 'community-list', community_list, 'rule', '10', 'regex', '65432']) self.cli_set(['policy', 'extcommunity-list', extcommunity_list, 'rule', '10', 'action', 'deny']) self.cli_set(['policy', 'extcommunity-list', extcommunity_list, 'rule', '10', 'regex', '65000']) self.cli_set(['policy', 'large-community-list', large_community_list, 'rule', '10', 'action', 'permit']) self.cli_set(['policy', 'large-community-list', large_community_list, 'rule', '10', 'regex', '100:200:300']) self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', '10', 'action', 'permit']) self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', '10', 'prefix', '192.0.2.0/24']) self.cli_set(['policy', 'prefix-list6', prefix_list, 'rule', '10', 'action', 'permit']) self.cli_set(['policy', 'prefix-list6', prefix_list, 'rule', '10', 'prefix', '2001:db8::/32']) for route_map, route_map_config in test_data.items(): path = base_path + ['route-map', route_map] self.cli_set(path + ['description', f'VyOS ROUTE-MAP {route_map}']) if 'rule' not in route_map_config: continue for rule, rule_config in route_map_config['rule'].items(): if 'action' in rule_config: self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) if 'call' in rule_config: self.cli_set(path + ['rule', rule, 'call', rule_config['call']]) if 'continue' in rule_config: self.cli_set(path + ['rule', rule, 'continue', rule_config['continue']]) if 'match' in rule_config: if 'as-path' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'as-path', rule_config['match']['as-path']]) if 'community' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'community', 'community-list', rule_config['match']['community']]) self.cli_set(path + ['rule', rule, 'match', 'community', 'exact-match']) if 'evpn-default-route' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'evpn', 'default-route']) if 'evpn-rd' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'evpn', 'rd', rule_config['match']['evpn-rd']]) if 'evpn-route-type' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'evpn', 'route-type', rule_config['match']['evpn-route-type']]) if 'evpn-vni' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'evpn', 'vni', rule_config['match']['evpn-vni']]) if 'extcommunity' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'extcommunity', rule_config['match']['extcommunity']]) if 'interface' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'interface', rule_config['match']['interface']]) if 'ip-address-acl' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'access-list', rule_config['match']['ip-address-acl']]) if 'ip-address-pfx' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'prefix-list', rule_config['match']['ip-address-pfx']]) if 'ip-address-pfx-len' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ip', 'address', 'prefix-len', rule_config['match']['ip-address-pfx-len']]) if 'ip-nexthop-acl' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'access-list', rule_config['match']['ip-nexthop-acl']]) if 'ip-nexthop-pfx' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'prefix-list', rule_config['match']['ip-nexthop-pfx']]) if 'ip-nexthop-addr' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'address', rule_config['match']['ip-nexthop-addr']]) if 'ip-nexthop-plen' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'prefix-len', rule_config['match']['ip-nexthop-plen']]) if 'ip-nexthop-type' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ip', 'nexthop', 'type', rule_config['match']['ip-nexthop-type']]) if 'ip-route-source-acl' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ip', 'route-source', 'access-list', rule_config['match']['ip-route-source-acl']]) if 'ip-route-source-pfx' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ip', 'route-source', 'prefix-list', rule_config['match']['ip-route-source-pfx']]) if 'ipv6-address-acl' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'access-list', rule_config['match']['ipv6-address-acl']]) if 'ipv6-address-pfx' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'prefix-list', rule_config['match']['ipv6-address-pfx']]) if 'ipv6-address-pfx-len' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'address', 'prefix-len', rule_config['match']['ipv6-address-pfx-len']]) if 'ipv6-nexthop-address' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'address', rule_config['match']['ipv6-nexthop-address']]) if 'ipv6-nexthop-access-list' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'access-list', rule_config['match']['ipv6-nexthop-access-list']]) if 'ipv6-nexthop-prefix-list' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'prefix-list', rule_config['match']['ipv6-nexthop-prefix-list']]) if 'ipv6-nexthop-type' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'ipv6', 'nexthop', 'type', rule_config['match']['ipv6-nexthop-type']]) if 'large-community' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'large-community', 'large-community-list', rule_config['match']['large-community']]) if 'local-pref' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'local-preference', rule_config['match']['local-pref']]) if 'metric' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'metric', rule_config['match']['metric']]) if 'origin-igp' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'origin', 'igp']) if 'origin-egp' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'origin', 'egp']) if 'origin-incomplete' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'origin', 'incomplete']) if 'peer' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'peer', rule_config['match']['peer']]) if 'rpki-invalid' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'rpki', 'invalid']) if 'rpki-not-found' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'rpki', 'notfound']) if 'rpki-valid' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'rpki', 'valid']) if 'protocol' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'protocol', rule_config['match']['protocol']]) + if 'source-vrf' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'source-vrf', rule_config['match']['source-vrf']]) if 'tag' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'tag', rule_config['match']['tag']]) if 'on-match' in rule_config: if 'goto' in rule_config['on-match']: self.cli_set(path + ['rule', rule, 'on-match', 'goto', rule_config['on-match']['goto']]) if 'next' in rule_config['on-match']: self.cli_set(path + ['rule', rule, 'on-match', 'next']) if 'set' in rule_config: if 'aggregator-as' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'aggregator', 'as', rule_config['set']['aggregator-as']]) if 'aggregator-ip' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'aggregator', 'ip', rule_config['set']['aggregator-ip']]) if 'as-path-exclude' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'as-path', 'exclude', rule_config['set']['as-path-exclude']]) if 'as-path-prepend' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'as-path', 'prepend', rule_config['set']['as-path-prepend']]) if 'atomic-aggregate' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'atomic-aggregate']) if 'distance' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'distance', rule_config['set']['distance']]) if 'ipv6-next-hop-global' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'ipv6-next-hop', 'global', rule_config['set']['ipv6-next-hop-global']]) if 'ipv6-next-hop-local' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'ipv6-next-hop', 'local', rule_config['set']['ipv6-next-hop-local']]) if 'ip-next-hop' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'ip-next-hop', rule_config['set']['ip-next-hop']]) if 'l3vpn' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'l3vpn-nexthop', 'encapsulation', 'gre']) if 'local-preference' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'local-preference', rule_config['set']['local-preference']]) if 'metric' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'metric', rule_config['set']['metric']]) if 'metric-type' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'metric-type', rule_config['set']['metric-type']]) if 'origin' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'origin', rule_config['set']['origin']]) if 'originator-id' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'originator-id', rule_config['set']['originator-id']]) if 'src' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'src', rule_config['set']['src']]) if 'tag' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'tag', rule_config['set']['tag']]) if 'weight' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'weight', rule_config['set']['weight']]) if 'evpn-gateway-ipv4' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'evpn', 'gateway', 'ipv4', rule_config['set']['evpn-gateway-ipv4']]) if 'evpn-gateway-ipv6' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'evpn', 'gateway', 'ipv6', rule_config['set']['evpn-gateway-ipv6']]) self.cli_commit() for route_map, route_map_config in test_data.items(): if 'rule' not in route_map_config: continue for rule, rule_config in route_map_config['rule'].items(): name = f'route-map {route_map} {rule_config["action"]} {rule}' config = self.getFRRconfig(name) self.assertIn(name, config) if 'call' in rule_config: tmp = 'call ' + rule_config['call'] self.assertIn(tmp, config) if 'continue' in rule_config: tmp = 'on-match goto ' + rule_config['continue'] self.assertIn(tmp, config) if 'match' in rule_config: if 'as-path' in rule_config['match']: tmp = 'match as-path ' + rule_config['match']['as-path'] self.assertIn(tmp, config) if 'community' in rule_config['match']: tmp = f'match community {rule_config["match"]["community"]} exact-match' self.assertIn(tmp, config) if 'evpn-default-route' in rule_config['match']: tmp = f'match evpn default-route' self.assertIn(tmp, config) if 'evpn-rd' in rule_config['match']: tmp = f'match evpn rd {rule_config["match"]["evpn-rd"]}' self.assertIn(tmp, config) if 'evpn-route-type' in rule_config['match']: tmp = f'match evpn route-type {rule_config["match"]["evpn-route-type"]}' self.assertIn(tmp, config) if 'evpn-vni' in rule_config['match']: tmp = f'match evpn vni {rule_config["match"]["evpn-vni"]}' self.assertIn(tmp, config) if 'extcommunity' in rule_config['match']: tmp = f'match extcommunity {rule_config["match"]["extcommunity"]}' self.assertIn(tmp, config) if 'interface' in rule_config['match']: tmp = f'match interface {rule_config["match"]["interface"]}' self.assertIn(tmp, config) if 'ip-address-acl' in rule_config['match']: tmp = f'match ip address {rule_config["match"]["ip-address-acl"]}' self.assertIn(tmp, config) if 'ip-address-pfx' in rule_config['match']: tmp = f'match ip address prefix-list {rule_config["match"]["ip-address-pfx"]}' self.assertIn(tmp, config) if 'ip-address-pfx-len' in rule_config['match']: tmp = f'match ip address prefix-len {rule_config["match"]["ip-address-pfx-len"]}' self.assertIn(tmp, config) if 'ip-nexthop-acl' in rule_config['match']: tmp = f'match ip next-hop {rule_config["match"]["ip-nexthop-acl"]}' self.assertIn(tmp, config) if 'ip-nexthop-pfx' in rule_config['match']: tmp = f'match ip next-hop prefix-list {rule_config["match"]["ip-nexthop-pfx"]}' self.assertIn(tmp, config) if 'ip-nexthop-addr' in rule_config['match']: tmp = f'match ip next-hop address {rule_config["match"]["ip-nexthop-addr"]}' self.assertIn(tmp, config) if 'ip-nexthop-plen' in rule_config['match']: tmp = f'match ip next-hop prefix-len {rule_config["match"]["ip-nexthop-plen"]}' self.assertIn(tmp, config) if 'ip-nexthop-type' in rule_config['match']: tmp = f'match ip next-hop type {rule_config["match"]["ip-nexthop-type"]}' self.assertIn(tmp, config) if 'ip-route-source-acl' in rule_config['match']: tmp = f'match ip route-source {rule_config["match"]["ip-route-source-acl"]}' self.assertIn(tmp, config) if 'ip-route-source-pfx' in rule_config['match']: tmp = f'match ip route-source prefix-list {rule_config["match"]["ip-route-source-pfx"]}' self.assertIn(tmp, config) if 'ipv6-address-acl' in rule_config['match']: tmp = f'match ipv6 address {rule_config["match"]["ipv6-address-acl"]}' self.assertIn(tmp, config) if 'ipv6-address-pfx' in rule_config['match']: tmp = f'match ipv6 address prefix-list {rule_config["match"]["ipv6-address-pfx"]}' self.assertIn(tmp, config) if 'ipv6-address-pfx-len' in rule_config['match']: tmp = f'match ipv6 address prefix-len {rule_config["match"]["ipv6-address-pfx-len"]}' self.assertIn(tmp, config) if 'ipv6-nexthop-address' in rule_config['match']: tmp = f'match ipv6 next-hop address {rule_config["match"]["ipv6-nexthop-address"]}' self.assertIn(tmp, config) if 'ipv6-nexthop-access-list' in rule_config['match']: tmp = f'match ipv6 next-hop {rule_config["match"]["ipv6-nexthop-access-list"]}' self.assertIn(tmp, config) if 'ipv6-nexthop-prefix-list' in rule_config['match']: tmp = f'match ipv6 next-hop prefix-list {rule_config["match"]["ipv6-nexthop-prefix-list"]}' self.assertIn(tmp, config) if 'ipv6-nexthop-type' in rule_config['match']: tmp = f'match ipv6 next-hop type {rule_config["match"]["ipv6-nexthop-type"]}' self.assertIn(tmp, config) if 'large-community' in rule_config['match']: tmp = f'match large-community {rule_config["match"]["large-community"]}' self.assertIn(tmp, config) if 'local-pref' in rule_config['match']: tmp = f'match local-preference {rule_config["match"]["local-pref"]}' self.assertIn(tmp, config) if 'metric' in rule_config['match']: tmp = f'match metric {rule_config["match"]["metric"]}' self.assertIn(tmp, config) if 'origin-igp' in rule_config['match']: tmp = f'match origin igp' self.assertIn(tmp, config) if 'origin-egp' in rule_config['match']: tmp = f'match origin egp' self.assertIn(tmp, config) if 'origin-incomplete' in rule_config['match']: tmp = f'match origin incomplete' self.assertIn(tmp, config) if 'peer' in rule_config['match']: tmp = f'match peer {rule_config["match"]["peer"]}' self.assertIn(tmp, config) if 'protocol' in rule_config['match']: tmp = f'match source-protocol {rule_config["match"]["protocol"]}' self.assertIn(tmp, config) if 'rpki-invalid' in rule_config['match']: tmp = f'match rpki invalid' self.assertIn(tmp, config) if 'rpki-not-found' in rule_config['match']: tmp = f'match rpki notfound' self.assertIn(tmp, config) if 'rpki-valid' in rule_config['match']: tmp = f'match rpki valid' self.assertIn(tmp, config) + if 'source-vrf' in rule_config['match']: + tmp = f'match source-vrf {rule_config["match"]["source-vrf"]}' + self.assertIn(tmp, config) if 'tag' in rule_config['match']: tmp = f'match tag {rule_config["match"]["tag"]}' self.assertIn(tmp, config) if 'on-match' in rule_config: if 'goto' in rule_config['on-match']: tmp = f'on-match goto {rule_config["on-match"]["goto"]}' self.assertIn(tmp, config) if 'next' in rule_config['on-match']: tmp = f'on-match next' self.assertIn(tmp, config) if 'set' in rule_config: tmp = ' set ' if 'aggregator-as' in rule_config['set']: tmp += 'aggregator as ' + rule_config['set']['aggregator-as'] elif 'aggregator-ip' in rule_config['set']: tmp += ' ' + rule_config['set']['aggregator-ip'] elif 'as-path-exclude' in rule_config['set']: tmp += 'as-path exclude ' + rule_config['set']['as-path-exclude'] elif 'as-path-prepend' in rule_config['set']: tmp += 'as-path prepend ' + rule_config['set']['as-path-prepend'] elif 'as-path-prepend-last-as' in rule_config['set']: tmp += 'as-path prepend last-as' + rule_config['set']['as-path-prepend-last-as'] elif 'atomic-aggregate' in rule_config['set']: 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 'l3vpn' in rule_config['set']: tmp += 'l3vpn next-hop encapsulation gre' elif 'local-preference' in rule_config['set']: tmp += 'local-preference ' + rule_config['set']['local-preference'] elif 'metric' in rule_config['set']: tmp += 'metric ' + rule_config['set']['metric'] elif 'metric-type' in rule_config['set']: tmp += 'metric-type ' + rule_config['set']['metric-type'] elif 'origin' in rule_config['set']: tmp += 'origin ' + rule_config['set']['origin'] elif 'originator-id' in rule_config['set']: tmp += 'originator-id ' + rule_config['set']['originator-id'] elif 'src' in rule_config['set']: tmp += 'src ' + rule_config['set']['src'] elif 'tag' in rule_config['set']: tmp += 'tag ' + rule_config['set']['tag'] elif 'weight' in rule_config['set']: tmp += 'weight ' + rule_config['set']['weight'] elif 'vpn-gateway-ipv4' in rule_config['set']: tmp += 'evpn gateway ipv4 ' + rule_config['set']['vpn-gateway-ipv4'] elif 'vpn-gateway-ipv6' in rule_config['set']: tmp += 'evpn gateway ipv6 ' + rule_config['set']['vpn-gateway-ipv6'] 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', 'address', src]) self.cli_commit() 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', 'address', 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 destination and protocol def test_protocol_destination_table_id(self): path = base_path + ['local-route'] dst = '203.0.113.12' rule = '85' table = '104' proto = 'tcp' self.cli_set(path + ['rule', rule, 'set', 'table', table]) self.cli_set(path + ['rule', rule, 'destination', 'address', dst]) self.cli_set(path + ['rule', rule, 'protocol', proto]) self.cli_commit() original = """ 85: from all to 203.0.113.12 ipproto tcp lookup 104 """ tmp = cmd('ip rule show prio 85') self.assertEqual(sort_ip(tmp), sort_ip(original)) # Test set table for destination, source, protocol, fwmark and port def test_protocol_port_address_fwmark_table_id(self): path = base_path + ['local-route'] dst = '203.0.113.5' src_list = ['203.0.113.1', '203.0.113.2'] rule = '23' fwmark = '123456' table = '123' new_table = '111' proto = 'udp' new_proto = 'tcp' src_port = '5555' dst_port = '8888' self.cli_set(path + ['rule', rule, 'set', 'table', table]) self.cli_set(path + ['rule', rule, 'destination', 'address', dst]) self.cli_set(path + ['rule', rule, 'source', 'port', src_port]) self.cli_set(path + ['rule', rule, 'protocol', proto]) self.cli_set(path + ['rule', rule, 'fwmark', fwmark]) self.cli_set(path + ['rule', rule, 'destination', 'port', dst_port]) for src in src_list: self.cli_set(path + ['rule', rule, 'source', 'address', src]) self.cli_commit() original = """ 23: from 203.0.113.1 to 203.0.113.5 fwmark 0x1e240 ipproto udp sport 5555 dport 8888 lookup 123 23: from 203.0.113.2 to 203.0.113.5 fwmark 0x1e240 ipproto udp sport 5555 dport 8888 lookup 123 """ tmp = cmd(f'ip rule show prio {rule}') self.assertEqual(sort_ip(tmp), sort_ip(original)) # Change table and protocol, delete fwmark and source port self.cli_delete(path + ['rule', rule, 'fwmark']) self.cli_delete(path + ['rule', rule, 'source', 'port']) self.cli_set(path + ['rule', rule, 'set', 'table', new_table]) self.cli_set(path + ['rule', rule, 'protocol', new_proto]) self.cli_commit() original = """ 23: from 203.0.113.1 to 203.0.113.5 ipproto tcp dport 8888 lookup 111 23: from 203.0.113.2 to 203.0.113.5 ipproto tcp dport 8888 lookup 111 """ tmp = cmd(f'ip rule show prio {rule}') 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', 'address', 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', 'address', 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', 'address', src]) self.cli_set(path + ['rule', rule, 'destination', 'address', 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', 'address', 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', 'address', 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', 'address', 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', 'address', 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', 'address', src]) self.cli_set(path + ['rule', rule, 'destination', 'address', 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', 'address', src]) self.cli_set(path + ['rule', rule, 'destination', 'address', 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', 'address', src]) self.cli_set(path_v6 + ['rule', rule, 'destination', 'address', 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', 'address', 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', 'address', 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 test_frr_individual_remove_T6283_T6250(self): path = base_path + ['route-map'] route_maps = ['RMAP-1', 'RMAP_2'] seq = '10' base_local_preference = 300 base_table = 50 # T6250 local_preference = base_local_preference table = base_table for route_map in route_maps: self.cli_set(path + [route_map, 'rule', seq, 'action', 'permit']) self.cli_set(path + [route_map, 'rule', seq, 'set', 'table', str(table)]) self.cli_set(path + [route_map, 'rule', seq, 'set', 'local-preference', str(local_preference)]) local_preference += 20 table += 5 self.cli_commit() local_preference = base_local_preference table = base_table for route_map in route_maps: config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit') self.assertIn(f' set local-preference {local_preference}', config) self.assertIn(f' set table {table}', config) local_preference += 20 table += 5 for route_map in route_maps: self.cli_delete(path + [route_map, 'rule', '10', 'set', 'table']) # we explicitly commit multiple times to be as vandal as possible to the system self.cli_commit() local_preference = base_local_preference for route_map in route_maps: config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit') self.assertIn(f' set local-preference {local_preference}', config) local_preference += 20 # T6283 seq = '20' prepend = '100 100 100' for route_map in route_maps: self.cli_set(path + [route_map, 'rule', seq, 'action', 'permit']) self.cli_set(path + [route_map, 'rule', seq, 'set', 'as-path', 'prepend', prepend]) self.cli_commit() for route_map in route_maps: config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit') self.assertIn(f' set as-path prepend {prepend}', config) for route_map in route_maps: self.cli_delete(path + [route_map, 'rule', seq, 'set']) # we explicitly commit multiple times to be as vandal as possible to the system self.cli_commit() for route_map in route_maps: config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit') self.assertNotIn(f' set', config) def sort_ip(output): o = '\n'.join([' '.join(line.strip().split()) for line in output.strip().splitlines()]) o = o.splitlines() o.sort() return o if __name__ == '__main__': unittest.main(verbosity=2)