diff options
37 files changed, 2806 insertions, 226 deletions
@@ -47,7 +47,6 @@ interface_definitions: $(config_xml_obj) rm -f $(TMPL_DIR)/vpn/node.def rm -f $(TMPL_DIR)/vpn/ipsec/node.def rm -rf $(TMPL_DIR)/vpn/nipsec - rm -rf $(TMPL_DIR)/protocols/nospf # XXX: required until OSPF and RIP is migrated from vyatta-cfg-quagga to vyos-1x mkdir $(TMPL_DIR)/interfaces/loopback/node.tag/ipv6 diff --git a/data/configd-include.json b/data/configd-include.json index 8369f0e13..751d8e012 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -28,12 +28,15 @@ "ipsec-settings.py", "lldp.py", "nat.py", +"nat66.py", "ntp.py", "policy-local-route.py", "protocols_bgp.py", "protocols_igmp.py", "protocols_isis.py", "protocols_mpls.py", +"protocols_ospf.py", +"protocols_ospfv3.py", "protocols_pim.py", "protocols_rip.py", "protocols_static_multicast.py", diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 5480447f2..499733225 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -118,7 +118,7 @@ {% endmacro %} # Start with clean NAT table -flush table nat +flush table ip nat {% if helper_functions == 'remove' %} {# NAT if going to be disabled - remove rules and targets from nftables #} {% set base_command = 'delete rule ip raw' %} diff --git a/data/templates/firewall/nftables-nat66.tmpl b/data/templates/firewall/nftables-nat66.tmpl new file mode 100644 index 000000000..b1a8f7a16 --- /dev/null +++ b/data/templates/firewall/nftables-nat66.tmpl @@ -0,0 +1,85 @@ +#!/usr/sbin/nft -f + +{% macro nptv6_rule(rule,config, chain) %} +{% set src_prefix = "ip6 saddr " + config.source.prefix if config.source is defined and config.source.prefix is defined and config.source.prefix is not none %} +{% set dest_address = "ip6 daddr " + config.destination.address if config.destination is defined and config.destination.address is defined and config.destination.address is not none %} +{% if chain == "PREROUTING" %} +{% set interface = " iifname \"" + config.inbound_interface + "\"" if config.inbound_interface is defined and config.inbound_interface != 'any' else '' %} +{% if config.translation.address | is_ip_network %} +{# support 1:1 network translation #} +{% set dnat_type = "dnat prefix to " %} +{% else %} +{% set dnat_type = "dnat to " %} +{% endif %} +{% set trns_address = dnat_type + config.translation.address if config.translation is defined and config.translation.address is defined and config.translation.address is not none %} +{% elif chain == "POSTROUTING" %} +{% set interface = " oifname \"" + config.outbound_interface + "\"" if config.outbound_interface is defined else '' %} +{% set trns_prefix = "snat prefix to " + config.translation.prefix if config.translation is defined and config.translation.prefix is defined and config.translation.prefix is not none %} +{% endif %} +{% set comment = "NPT-NAT-" + rule %} +{% if rule.log %} +{% set base_log = "[NPT-DST-" + rule %} +{% set log = base_log + "]" %} +{% endif %} +{% set output = "add rule ip6 nat " + chain + interface %} +{# Count packets #} +{% set output = output + " counter" %} +{# Special handling of log option, we must repeat the entire rule before the #} +{# NAT translation options are added, this is essential #} +{% if log %} +{% set log_output = output + " log prefix \"" + log + "\" comment \"" + comment + "\"" %} +{% endif %} +{% if src_prefix %} +{% set output = output + " " + src_prefix %} +{% endif %} +{% if dest_address %} +{% set output = output + " " + dest_address %} +{% endif %} +{% if trns_prefix %} +{% set output = output + " " + trns_prefix %} +{% endif %} +{% if trns_address %} +{% set output = output + " " + trns_address %} +{% endif %} +{% if comment %} +{% set output = output + " comment \"" + comment + "\"" %} +{% endif %} +{{ log_output if log_output }} +{{ output }} +{% endmacro %} + +# Start with clean NAT table +flush table ip6 nat +{% if helper_functions == 'remove' %} +{# NAT if going to be disabled - remove rules and targets from nftables #} +{% set base_command = "delete rule ip6 raw" %} +{{base_command}} PREROUTING handle {{ pre_ct_conntrack }} +{{base_command}} OUTPUT handle {{ out_ct_conntrack }} + +delete chain ip6 raw NAT_CONNTRACK + +{% elif helper_functions == 'add' %} +{# NAT if enabled - add targets to nftables #} +add chain ip6 raw NAT_CONNTRACK +add rule ip6 raw NAT_CONNTRACK counter accept +{% set base_command = "add rule ip6 raw" %} +{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK +{{ base_command }} OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK +{% endif %} + +# +# Destination NAT66 rules build up here +# +{% if destination is defined and destination.rule is defined and destination.rule is not none %} +{% for rule, config in destination.rule.items() if config.disable is not defined %} +{{ nptv6_rule(rule, config, 'PREROUTING') }} +{% endfor %} +{% endif %} +# +# Source NAT66 rules build up here +# +{% if source is defined and source.rule is defined and source.rule is not none %} +{% for rule, config in source.rule.items() if config.disable is not defined %} +{{ nptv6_rule(rule, config, 'POSTROUTING') }} +{% endfor %} +{% endif %} diff --git a/data/templates/frr/bgp.frr.tmpl b/data/templates/frr/bgp.frr.tmpl index 6fe88151f..523fe75e3 100644 --- a/data/templates/frr/bgp.frr.tmpl +++ b/data/templates/frr/bgp.frr.tmpl @@ -52,6 +52,22 @@ {% if config.update_source is defined and config.update_source is not none %} neighbor {{ neighbor }} update-source {{ config.update_source }} {% endif %} +{% if config.interface is defined and config.interface is not none %} +{% if config.interface.peer_group is defined and config.interface.peer_group is not none %} + neighbor {{ neighbor }} interface peer-group {{ config.interface.peer_group }} +{% endif %} +{% if config.interface.remote_as is defined and config.interface.remote_as is not none %} + neighbor {{ neighbor }} interface remote-as {{ config.interface.remote_as }} +{% endif %} +{% if config.interface.v6only is defined and config.interface.v6only is not none %} +{% if config.interface.v6only.peer_group is defined and config.interface.v6only.peer_group is not none %} + neighbor {{ neighbor }} interface v6only peer-group {{ config.interface.v6only.peer_group }} +{% endif %} +{% if config.interface.v6only.remote_as is defined and config.interface.v6only.remote_as is not none %} + neighbor {{ neighbor }} interface v6only remote-as {{ config.interface.v6only.remote_as }} +{% endif %} +{% endif %} +{% endif %} ! {% if config.address_family is defined and config.address_family is not none %} {% for af in config.address_family %} @@ -84,14 +100,16 @@ {% if config.address_family[af].distribute_list is defined and config.address_family[af].distribute_list is not none %} {% if config.address_family[af].distribute_list.export is defined and config.address_family[af].distribute_list.export is not none %} neighbor {{ neighbor }} distribute-list {{ config.address_family[af].distribute_list.export }} out -{% elif config.address_family[af].distribute_list.import is defined and config.address_family[af].distribute_list.import is not none %} - neighbor {{ neighbor }} distribute-list {{ config.address_family[af].distribute_list.export }} in +{% endif %} +{% if config.address_family[af].distribute_list.import is defined and config.address_family[af].distribute_list.import is not none %} + neighbor {{ neighbor }} distribute-list {{ config.address_family[af].distribute_list.import }} in {% endif %} {% endif %} {% if config.address_family[af].filter_list is defined and config.address_family[af].filter_list is not none %} {% if config.address_family[af].filter_list.export is defined and config.address_family[af].filter_list.export is not none %} neighbor {{ neighbor }} filter-list {{ config.address_family[af].filter_list.export }} out -{% elif config.address_family[af].filter_list.import is defined and config.address_family[af].filter_list.import is not none %} +{% endif %} +{% if config.address_family[af].filter_list.import is defined and config.address_family[af].filter_list.import is not none %} neighbor {{ neighbor }} filter-list {{ config.address_family[af].filter_list.import }} in {% endif %} {% endif %} @@ -108,14 +126,16 @@ {% if config.address_family[af].route_map is defined and config.address_family[af].route_map is not none %} {% if config.address_family[af].route_map.export is defined and config.address_family[af].route_map.export is not none %} neighbor {{ neighbor }} route-map {{ config.address_family[af].route_map.export }} out -{% elif config.address_family[af].route_map.import is defined and config.address_family[af].route_map.import is not none %} +{% endif %} +{% if config.address_family[af].route_map.import is defined and config.address_family[af].route_map.import is not none %} neighbor {{ neighbor }} route-map {{ config.address_family[af].route_map.import }} in {% endif %} {% endif %} {% if config.address_family[af].prefix_list is defined and config.address_family[af].prefix_list is not none %} {% if config.address_family[af].prefix_list.export is defined and config.address_family[af].prefix_list.export is not none %} neighbor {{ neighbor }} prefix-list {{ config.address_family[af].prefix_list.export }} out -{% elif config.address_family[af].prefix_list.import is defined and config.address_family[af].prefix_list.import is not none %} +{% endif %} +{% if config.address_family[af].prefix_list.import is defined and config.address_family[af].prefix_list.import is not none %} neighbor {{ neighbor }} prefix-list {{ config.address_family[af].prefix_list.import }} in {% endif %} {% endif %} diff --git a/data/templates/frr/ospf.frr.tmpl b/data/templates/frr/ospf.frr.tmpl index 465034f15..07699290c 100644 --- a/data/templates/frr/ospf.frr.tmpl +++ b/data/templates/frr/ospf.frr.tmpl @@ -1,2 +1,131 @@ ! +router ospf +{% if access_list is defined and access_list is not none %} +{% for acl, acl_config in access_list.items() %} +{% for protocol in acl_config.export if acl_config.export is defined %} + distribute-list {{ acl }} out {{ protocol }} +{% endfor %} +{% endfor %} +{% endif %} +{% if area is defined and area is not none %} +{% for area_id, area_config in area.items() %} +{% if area_config.area_type is defined and area_config.area_type is not none %} +{% for type, type_config in area_config.area_type.items() if type != 'normal' %} + area {{ area_id }} {{ type }} {{ 'no-summary' if type_config.no_summary is defined }} +{% if type_config.default_cost is defined and type_config.default_cost is not none %} + area {{ area_id }} default-cost {{ type_config.default_cost }} +{% endif %} +{% endfor %} +{% endif %} +{% if area_config.authentication is defined and area_config.authentication is not none %} + area {{ area_id }} authentication {{ 'message-digest' if area_config.authentication == 'md5' }} +{% endif %} +{% for network in area_config.network if area_config.network is defined %} + network {{ network }} area {{ area_id }} +{% endfor %} +{% if area_config.range is defined and area_config.range is not none %} +{% for range, range_config in area_config.range.items() %} +{% if range_config.cost is defined and range_config.cost is not none %} + area {{ area_id }} range {{ range }} cost {{ range_config.cost }} +{% endif %} +{% if range_config.not_advertise is defined %} + area {{ area_id }} range {{ range }} not-advertise +{% endif %} +{% if range_config.substitute is defined and range_config.substitute is not none %} + area {{ area_id }} range {{ range }} substitute {{ range_config.substitute }} +{% endif %} +{% endfor %} +{% endif %} +{% if area_config.shortcut is defined and area_config.shortcut is not none %} + area {{ area_id }} shortcut {{ area_config.shortcut }} +{% endif %} +{% if area_config.virtual_link is defined and area_config.virtual_link is not none %} +{% for link, link_config in area_config.virtual_link.items() %} +{% if link_config.authentication is defined and link_config.authentication is not none %} +{% if link_config.authentication.plaintext_password is defined and link_config.authentication.plaintext_password is not none %} + area {{ area_id }} virtual-link {{ link }} authentication-key {{ link_config.authentication.plaintext_password }} +{% elif link_config.authentication.md5 is defined and link_config.authentication.md5.key_id is defined and link_config.authentication.md5.key_id is not none %} +{% for key, key_config in link_config.authentication.md5.key_id.items() %} + area {{ area_id }} virtual-link {{ link }} message-digest-key {{ key }} md5 {{ key_config.md5_key }} +{% endfor %} +{% endif %} +{% endif %} +{% if link_config.dead_interval is defined and link_config.dead_interval is not none %} +{# The following values are default values #} + area {{ area_id }} virtual-link {{ link }} hello-interval {{ link_config.hello_interval }} retransmit-interval {{ link_config.retransmit_interval }} transmit-delay {{ link_config.retransmit_interval }} dead-interval {{ link_config.dead_interval }} +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} +{% if auto_cost is defined and auto_cost.reference_bandwidth is defined and auto_cost.reference_bandwidth is not none %} + auto-cost reference-bandwidth {{ auto_cost.reference_bandwidth }} +{% endif %} +{% if default_information is defined and default_information.originate is defined and default_information.originate is not none %} + default-information originate {{ 'always' if default_information.originate.always is defined }} {{ 'metric ' + default_information.originate.metric if default_information.originate.metric is defined }} {{ 'metric-type ' + default_information.originate.metric_type if default_information.originate.metric_type is defined }} {{ 'route-map ' + default_information.originate.route_map if default_information.originate.route_map is defined }} +{% endif %} +{% if default_metric is defined and default_metric is not none %} + default-metric {{ default_metric }} +{% endif %} +{% if distance is defined and distance is not none %} +{% if distance.global is defined and distance.global is not none %} + distance {{ distance.global }} +{% endif %} +{% if distance.ospf is defined and distance.ospf is not none %} + distance ospf {{ 'intra-area ' + distance.ospf.intra_area if distance.ospf.intra_area is defined }} {{ 'inter-area ' + distance.ospf.inter_area if distance.ospf.inter_area is defined }} {{ 'external ' + distance.ospf.external if distance.ospf.external is defined }} +{% endif %} +{% endif %} +{% if log_adjacency_changes is defined %} + log-adjacency-changes {{ "detail" if log_adjacency_changes.detail is defined }} +{% endif %} +{% if max_metric is defined and max_metric.router_lsa is defined and max_metric.router_lsa is not none %} +{% if max_metric.router_lsa.administrative is defined %} + max-metric router-lsa administrative +{% endif %} +{% if max_metric.router_lsa.on_shutdown is defined and max_metric.router_lsa.on_shutdown is not none %} + max-metric router-lsa on-shutdown {{ max_metric.router_lsa.on_shutdown }} +{% endif %} +{% if max_metric.router_lsa.on_startup is defined and max_metric.router_lsa.on_startup is not none %} + max-metric router-lsa on-startup {{ max_metric.router_lsa.on_startup }} +{% endif %} +{% endif %} +{% if mpls_te is defined and mpls_te.enable is defined %} + mpls-te on + mpls-te router-address {{ mpls_te.router_address }} +{% endif %} +{% if neighbor is defined and neighbor is not none%} +{% for address, address_config in neighbor.items() %} + neighbor {{ address }} {{ 'priority ' + address_config.priority if address_config.priority is defined }} {{ 'poll-interval ' + address_config.poll_interval if address_config.poll_interval is defined }} +{% endfor %} +{% endif %} +{% if parameters is defined and parameters is not none %} +{% if parameters.abr_type is defined and parameters.abr_type is not none %} + ospf abr-type {{ parameters.abr_type }} +{% endif %} +{% if parameters.router_id is defined and parameters.router_id is not none %} + ospf router-id {{ parameters.router_id }} +{% endif %} +{% endif %} +{% for interface in passive_interface if passive_interface is defined %} + passive-interface {{ interface }} +{% endfor %} +{% for interface in passive_interface_exclude if passive_interface_exclude is defined %} + no passive-interface {{ interface }} +{% endfor %} +{% if redistribute is defined and redistribute is not none %} +{% for protocol, options in redistribute.items() %} + redistribute {{ protocol }} {{ 'metric ' + options.metric if options.metric is defined }} {{ 'metric-type ' + options.metric_type if options.metric_type is defined }} {{ 'route-map ' + options.route_map if options.route_map is defined }} +{% endfor %} +{% endif %} +{% if refresh is defined and refresh.timers is defined and refresh.timers is not none %} + refresh timer {{ refresh.timers }} +{% endif %} +{% if timers is defined and timers.throttle is defined and timers.throttle.spf is defined and timers.throttle.spf is not none %} +{# Timer values have default values #} + timers throttle spf {{ timers.throttle.spf.delay }} {{ timers.throttle.spf.initial_holdtime }} {{ timers.throttle.spf.max_holdtime }} +{% endif %} +! +{% if route_map is defined and route_map is not none %} +ip protocol ospf route-map {{ route_map }} +{% endif %} ! diff --git a/data/templates/frr/ospfv3.frr.tmpl b/data/templates/frr/ospfv3.frr.tmpl new file mode 100644 index 000000000..c63ef80dc --- /dev/null +++ b/data/templates/frr/ospfv3.frr.tmpl @@ -0,0 +1,41 @@ +! +router ospf6 +{% if area is defined and area is not none %} +{% for area_id, area_config in area.items() %} +{% if area_config.interface is defined and area_config.interface is not none %} +{% for interface in area_config.interface %} + interface {{ interface }} area {{ area_id }} +{% endfor %} +{% endif %} +{% if area_config.range is defined and area_config.range is not none %} +{% for prefix, prefix_config in area_config.range.items() %} + area {{ area_id }} range {{ prefix }} {{ 'advertise' if prefix_config.advertise is defined }} {{ 'not-advertise' if prefix_config.not_advertise is defined }} +{% endfor %} +{% endif %} +{% if area_config.export_list is defined and area_config.export_list is not none %} + area {{ area_id }} export-list {{ area_config.export_list }} +{% endif %} +{% if area_config.import_list is defined and area_config.import_list is not none %} + area {{ area_id }} import-list {{ area_config.import_list }} +{% endif %} +{% endfor %} +{% endif %} +{% if distance is defined and distance is not none %} +{% if distance.global is defined and distance.global is not none %} + distance {{ distance.global }} +{% endif %} +{% if distance.ospfv3 is defined and distance.ospfv3 is not none %} + distance ospf6 {{ 'intra-area ' + distance.ospfv3.intra_area if distance.ospfv3.intra_area is defined }} {{ 'inter-area ' + distance.ospfv3.inter_area if distance.ospfv3.inter_area is defined }} {{ 'external ' + distance.ospfv3.external if distance.ospfv3.external is defined }} +{% endif %} +{% endif %} +{% if parameters is defined and parameters is not none %} +{% if parameters.router_id is defined and parameters.router_id is not none %} + ospf6 router-id {{ parameters.router_id }} +{% endif %} +{% endif %} +{% if redistribute is defined and redistribute is not none %} +{% for protocol, options in redistribute.items() %} + redistribute {{ protocol }} {{ 'route-map ' + options.route_map if options.route_map is defined }} +{% endfor %} +{% endif %} +! diff --git a/data/templates/proxy-ndp/ndppd.conf.tmpl b/data/templates/proxy-ndp/ndppd.conf.tmpl new file mode 100644 index 000000000..0137d8135 --- /dev/null +++ b/data/templates/proxy-ndp/ndppd.conf.tmpl @@ -0,0 +1,44 @@ +######################################################## +# +# autogenerated by nat66.py +# +# The configuration file must define one upstream +# interface. +# +# For some services, such as nat66, because it runs +# stateless, it needs to rely on NDP Proxy to respond +# to NDP requests. +# +# When using nat66 source rules, NDP Proxy needs +# to be enabled +# +######################################################## + +{% set global = namespace(ndppd_interfaces = [],ndppd_prefixs = []) %} +{% if source is defined and source.rule is defined and source.rule is not none %} +{% for rule, config in source.rule.items() if config.disable is not defined %} +{% if config.outbound_interface is defined %} +{% if config.outbound_interface not in global.ndppd_interfaces %} +{% set global.ndppd_interfaces = global.ndppd_interfaces + [config.outbound_interface] %} +{% endif %} +{% if config.translation.prefix is defined %} +{% set global.ndppd_prefixs = global.ndppd_prefixs + [{'interface':config.outbound_interface,'rule':config.translation.prefix}] %} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} + +{% for interface in global.ndppd_interfaces %} +proxy {{ interface }} { + router yes + timeout 500 + ttl 30000 +{% for map in global.ndppd_prefixs %} +{% if map.interface == interface %} + rule {{ map.rule }} { + static + } +{% endif %} +{% endfor %} +} +{% endfor %} diff --git a/debian/control b/debian/control index d0ba72bcf..2c8ee3d43 100644 --- a/debian/control +++ b/debian/control @@ -129,7 +129,8 @@ Depends: wide-dhcpv6-client, wireguard-tools, wireless-regdb, - wpasupplicant (>= 0.6.7) + wpasupplicant (>= 0.6.7), + ndppd Description: VyOS configuration scripts and data VyOS configuration scripts, interface definitions, and everything diff --git a/interface-definitions/arp.xml.in b/interface-definitions/arp.xml.in index b72f025a8..082afe00f 100644 --- a/interface-definitions/arp.xml.in +++ b/interface-definitions/arp.xml.in @@ -20,7 +20,7 @@ <properties> <help>mac address to translate to</help> <valueHelp> - <format>h:h:h:h:h:h</format> + <format>macaddr</format> <description>Hardware (MAC) address</description> </valueHelp> <constraint> diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in index aba2bea8a..28b61e92d 100644 --- a/interface-definitions/dhcp-server.xml.in +++ b/interface-definitions/dhcp-server.xml.in @@ -337,7 +337,7 @@ <properties> <help>Media Access Control (MAC) address</help> <valueHelp> - <format>h:h:h:h:h:h</format> + <format>macaddr</format> <description>Hardware (MAC) address</description> </valueHelp> <constraint> diff --git a/interface-definitions/include/interface-hw-id.xml.i b/interface-definitions/include/interface-hw-id.xml.i index af58fae3c..55ef55add 100644 --- a/interface-definitions/include/interface-hw-id.xml.i +++ b/interface-definitions/include/interface-hw-id.xml.i @@ -3,8 +3,8 @@ <properties> <help>Associate Ethernet Interface with given Media Access Control (MAC) address</help> <valueHelp> - <format>h:h:h:h:h:h</format> - <description>Hardware Media Access Control (MAC) address</description> + <format>macaddr</format> + <description>Hardware (MAC) address</description> </valueHelp> <constraint> <validator name="mac-address"/> diff --git a/interface-definitions/include/ospf-metric-type.xml.i b/interface-definitions/include/ospf-metric-type.xml.i new file mode 100644 index 000000000..50f11960c --- /dev/null +++ b/interface-definitions/include/ospf-metric-type.xml.i @@ -0,0 +1,15 @@ +<!-- included start from ospf-metric-type.xml.i --> +<leafNode name="metric-type"> + <properties> + <help>OSPF metric type for default routes (default: 2)</help> + <valueHelp> + <format>u32:1-2</format> + <description>Metric type for default routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2"/> + </constraint> + </properties> + <defaultValue>2</defaultValue> +</leafNode> +<!-- included end --> diff --git a/interface-definitions/include/ospf-metric.xml.i b/interface-definitions/include/ospf-metric.xml.i index 771fda02d..3ce12e877 100644 --- a/interface-definitions/include/ospf-metric.xml.i +++ b/interface-definitions/include/ospf-metric.xml.i @@ -11,23 +11,4 @@ </constraint> </properties> </leafNode> -<leafNode name="metric-type"> - <properties> - <help>OSPF metric type for default routes</help> - <valueHelp> - <format>u32:1-2</format> - <description>Metric type for default routes (default 2)</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-2"/> - </constraint> - </properties> -</leafNode> -<leafNode name="route-map"> - <properties> - <help>Route map reference</help> - <completionHelp> - <path>policy route-map</path> - </completionHelp> - </properties> -</leafNode> +<!-- included end --> diff --git a/interface-definitions/include/ospf-route-map.xml.i b/interface-definitions/include/ospf-route-map.xml.i new file mode 100644 index 000000000..8dc5b37da --- /dev/null +++ b/interface-definitions/include/ospf-route-map.xml.i @@ -0,0 +1,10 @@ +<!-- included start from ospf-route-map.xml.i --> +<leafNode name="route-map"> + <properties> + <help>Route map reference</help> + <completionHelp> + <path>policy route-map</path> + </completionHelp> + </properties> +</leafNode> +<!-- included end --> diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in index d6bed5b27..3cff8abc9 100644 --- a/interface-definitions/nat.xml.in +++ b/interface-definitions/nat.xml.in @@ -56,73 +56,6 @@ </tagNode> </children> </node> - <node name="nptv6"> - <properties> - <help>IPv6-to-IPv6 Network Prefix Translation Settings</help> - </properties> - <children> - <tagNode name="rule"> - <properties> - <help>NPTv6 rule number</help> - <valueHelp> - <format>1-999999</format> - <description>Number for this rule</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-999999"/> - </constraint> - <constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage> - </properties> - <children> - <leafNode name="description"> - <properties> - <help>Rule description</help> - </properties> - </leafNode> - #include <include/generic-disable-node.xml.i> - #include <include/nat-interface.xml.i> - <node name="source"> - <properties> - <help>IPv6 source prefix options</help> - </properties> - <children> - <leafNode name="prefix"> - <properties> - <help>IPv6 prefix to be translated</help> - <valueHelp> - <format>ipv6net</format> - <description>IPv6 prefix</description> - </valueHelp> - <constraint> - <validator name="ipv6-prefix"/> - </constraint> - </properties> - </leafNode> - </children> - </node> - <node name="translation"> - <properties> - <help>Translated IPv6 prefix options</help> - </properties> - <children> - <leafNode name="prefix"> - <properties> - <help>IPv6 prefix to translate to</help> - <valueHelp> - <format>ipv6net</format> - <description>IPv6 prefix</description> - </valueHelp> - <constraint> - <validator name="ipv6-prefix"/> - </constraint> - </properties> - </leafNode> - </children> - </node> - </children> - </tagNode> - </children> - </node> <node name="source"> <properties> <help>Source NAT settings</help> diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in new file mode 100644 index 000000000..b45ebc0a8 --- /dev/null +++ b/interface-definitions/nat66.xml.in @@ -0,0 +1,192 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="nat66" owner="${vyos_conf_scripts_dir}/nat66.py"> + <properties> + <help>IPv6-to-IPv6 Network Prefix Translation (NAT66/NPT) Settings</help> + <priority>220</priority> + </properties> + <children> + <node name="source"> + <properties> + <help>Prefix mapping of IPv6 source address translation</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Source NAT66 rule number</help> + <valueHelp> + <format>1-999999</format> + <description>Number for this rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>NAT66 rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + <leafNode name="description"> + <properties> + <help>Rule description</help> + </properties> + </leafNode> + <leafNode name="disable"> + <properties> + <help>Disable NAT66 rule</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="log"> + <properties> + <help>NAT66 rule logging</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="outbound-interface"> + <properties> + <help>Outbound interface of NAT66 traffic</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + </properties> + </leafNode> + <node name="source"> + <properties> + <help>IPv6 source prefix options</help> + </properties> + <children> + <leafNode name="prefix"> + <properties> + <help>IPv6 prefix to be translated</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="translation"> + <properties> + <help>Translated IPv6 prefix options</help> + </properties> + <children> + <leafNode name="prefix"> + <properties> + <help>IPv6 prefix to translate to</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + <node name="destination"> + <properties> + <help>Prefix mapping for IPv6 destination address translation</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Destination NAT66 rule number</help> + <valueHelp> + <format>1-999999</format> + <description>Number for this rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>NAT66 rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + <leafNode name="description"> + <properties> + <help>Rule description</help> + </properties> + </leafNode> + <leafNode name="disable"> + <properties> + <help>Disable NAT66 rule</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="log"> + <properties> + <help>NAT66 rule logging</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="inbound-interface"> + <properties> + <help>Inbound interface of NAT66 traffic</help> + <completionHelp> + <list>any</list> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + </properties> + </leafNode> + <node name="destination"> + <properties> + <help>IPv6 destination prefix options</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv6 address or prefix to be translated</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="translation"> + <properties> + <help>Translated IPv6 address options</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>IPv6 address or prefix to translate to</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols-bgp.xml.in b/interface-definitions/protocols-bgp.xml.in index ea879a321..3edacb0ca 100644 --- a/interface-definitions/protocols-bgp.xml.in +++ b/interface-definitions/protocols-bgp.xml.in @@ -269,10 +269,15 @@ </leafNode> <tagNode name="range"> <properties> - <help>IPv4/IPv6 prefix range</help> - <completionHelp> - <list><x.x.x.x/x> <h:h:h:h:h:h:h:h/h></list> - </completionHelp> + <help>BGP dynamic neighbors listen range</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 dynamic neighbors listen range</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 dynamic neighbors listen range</description> + </valueHelp> <constraint> <validator name="ipv4-prefix"/> <validator name="ipv6-prefix"/> diff --git a/interface-definitions/protocols-isis.xml.in b/interface-definitions/protocols-isis.xml.in index 8c5691ecf..4ac378977 100644 --- a/interface-definitions/protocols-isis.xml.in +++ b/interface-definitions/protocols-isis.xml.in @@ -257,7 +257,7 @@ <valueless/> </properties> </leafNode> -<!-- +<!-- <node name="inter-as"> <properties> <help>MPLS traffic engineering inter-AS support</help> @@ -394,9 +394,18 @@ <tagNode name="prefix"> <properties> <help>Static IPv4/IPv6 prefix segment/label mapping</help> - <completionHelp> - <list><x.x.x.x/x> <h:h:h:h:h:h:h:h/h></list> - </completionHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix segment</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 prefix segment</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv6-prefix"/> + </constraint> </properties> <children> <node name="absolute"> diff --git a/interface-definitions/protocols-ospf.xml.in b/interface-definitions/protocols-ospf.xml.in index 4d5b84be0..074d0db63 100644 --- a/interface-definitions/protocols-ospf.xml.in +++ b/interface-definitions/protocols-ospf.xml.in @@ -3,15 +3,18 @@ <interfaceDefinition> <node name="protocols"> <children> - <node name="nospf" owner="${vyos_conf_scripts_dir}/protocols_ospf.py"> + <node name="ospf" owner="${vyos_conf_scripts_dir}/protocols_ospf.py"> <properties> - <help>Open Shortest Path First protocol (OSPF) parameters</help> + <help>Open Shortest Path First (OSPF)</help> <priority>620</priority> </properties> <children> <tagNode name="access-list"> <properties> <help>Access list to filter networks in routing updates</help> + <completionHelp> + <path>policy access-list</path> + </completionHelp> <valueHelp> <format>u32</format> <description>Access-list number</description> @@ -109,7 +112,7 @@ </leafNode> <leafNode name="translate"> <properties> - <help>Nssa-abr</help> + <help>Configure NSSA-ABR (default: candidate)</help> <completionHelp> <list>always candidate never</list> </completionHelp> @@ -129,6 +132,7 @@ <regex>^(always|candidate|never)$</regex> </constraint> </properties> + <defaultValue>candidate</defaultValue> </leafNode> </children> </node> @@ -194,6 +198,13 @@ <tagNode name="range"> <properties> <help>Summarize routes matching prefix (border routers only)</help> + <valueHelp> + <format>ipv4net</format> + <description>Area range prefix</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> </properties> <children> <leafNode name="cost"> @@ -254,6 +265,14 @@ <tagNode name="virtual-link"> <properties> <help>Virtual link</help> + <valueHelp> + <format>ipv4</format> + <description>OSPF area in dotted decimal notation</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <validator name="ip-address"/> + </constraint> </properties> <children> <node name="authentication"> @@ -301,7 +320,7 @@ </node> <leafNode name="dead-interval"> <properties> - <help>Interval after which a neighbor is declared dead</help> + <help>Interval after which a neighbor is declared dead (default: 40)</help> <valueHelp> <format>u32:1-65535</format> <description>Neighbor dead interval (seconds)</description> @@ -310,10 +329,11 @@ <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> + <defaultValue>40</defaultValue> </leafNode> <leafNode name="hello-interval"> <properties> - <help>Interval between hello packets</help> + <help>Interval between hello packets (default: 10)</help> <valueHelp> <format>u32:1-65535</format> <description>Hello interval (seconds)</description> @@ -322,10 +342,11 @@ <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> + <defaultValue>10</defaultValue> </leafNode> <leafNode name="retransmit-interval"> <properties> - <help>Interval between retransmitting lost link state advertisements</help> + <help>Interval between retransmitting lost link state advertisements (default: 5)</help> <valueHelp> <format>u32:1-65535</format> <description>Retransmit interval (seconds)</description> @@ -334,10 +355,11 @@ <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> + <defaultValue>5</defaultValue> </leafNode> <leafNode name="transmit-delay"> <properties> - <help>Link state transmit delay</help> + <help>Link state transmit delay (default: 1)</help> <valueHelp> <format>u32:1-65535</format> <description>Link state transmit delay (seconds)</description> @@ -346,6 +368,7 @@ <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> + <defaultValue>1</defaultValue> </leafNode> </children> </tagNode> @@ -353,7 +376,7 @@ </tagNode> <node name="auto-cost"> <properties> - <help>Calculate OSPF interface cost according to bandwidth</help> + <help>Calculate OSPF interface cost according to bandwidth (default: 100)</help> </properties> <children> <leafNode name="reference-bandwidth"> @@ -361,12 +384,13 @@ <help>Reference bandwidth method to assign OSPF cost</help> <valueHelp> <format>u32:1-4294967</format> - <description>Reference bandwidth cost in Mbits/sec (default 100)</description> + <description>Reference bandwidth cost in Mbits/sec</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-4294967"/> </constraint> </properties> + <defaultValue>100</defaultValue> </leafNode> </children> </node> @@ -386,38 +410,9 @@ <valueless/> </properties> </leafNode> - <leafNode name="metric"> - <properties> - <help>OSPF default metric</help> - <valueHelp> - <format>u32:0-16777214</format> - <description>Default metric</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-16777214"/> - </constraint> - </properties> - </leafNode> - <leafNode name="metric-type"> - <properties> - <help>OSPF metric type for default routes</help> - <valueHelp> - <format>u32:1-2</format> - <description>Metric type for default routes (default 2)</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-2"/> - </constraint> - </properties> - </leafNode> - <leafNode name="route-map"> - <properties> - <help>Route map reference</help> - <completionHelp> - <path>policy route-map</path> - </completionHelp> - </properties> - </leafNode> + #include <include/ospf-metric.xml.i> + #include <include/ospf-metric-type.xml.i> + #include <include/ospf-route-map.xml.i> </children> </node> </children> @@ -529,11 +524,11 @@ <properties> <help>Advertise stub-router prior to full shutdown of OSPF</help> <valueHelp> - <format>u32:5-86400</format> + <format>u32:5-100</format> <description>Time (seconds) to advertise self as stub-router</description> </valueHelp> <constraint> - <validator name="numeric" argument="--range 5-86400"/> + <validator name="numeric" argument="--range 5-100"/> </constraint> </properties> </leafNode> @@ -575,41 +570,47 @@ <validator name="ipv4-address"/> </constraint> </properties> + <defaultValue>0.0.0.0</defaultValue> </leafNode> </children> </node> <tagNode name="neighbor"> <properties> - <help>Neighbor IP address</help> + <help>Specify neighbor router</help> <valueHelp> <format>ipv4</format> <description>Neighbor IP address</description> </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> </properties> <children> <leafNode name="poll-interval"> <properties> - <help>Dead neighbor polling interval</help> + <help>Dead neighbor polling interval (default: 60)</help> <valueHelp> <format>u32:1-65535</format> - <description>Seconds between dead neighbor polling interval (default 60)</description> + <description>Seconds between dead neighbor polling interval</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> + <defaultValue>60</defaultValue> </leafNode> <leafNode name="priority"> <properties> - <help>Neighbor priority in seconds</help> + <help>Neighbor priority in seconds (default: 0)</help> <valueHelp> <format>u32:0-255</format> - <description>Neighbor priority (default 0)</description> + <description>Neighbor priority</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-255"/> </constraint> </properties> + <defaultValue>0</defaultValue> </leafNode> </children> </tagNode> @@ -620,7 +621,7 @@ <children> <leafNode name="abr-type"> <properties> - <help>OSPF ABR type</help> + <help>OSPF ABR type (default: cisco)</help> <completionHelp> <list>cisco ibm shortcut standard</list> </completionHelp> @@ -644,6 +645,7 @@ <regex>^(cisco|ibm|shortcut|standard)$</regex> </constraint> </properties> + <defaultValue>cisco</defaultValue> </leafNode> <leafNode name="opaque-lsa"> <properties> @@ -674,31 +676,37 @@ <leafNode name="passive-interface"> <properties> <help>Suppress routing updates on an interface</help> + <completionHelp> + <list>default</list> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> <valueHelp> - <format><interface></format> + <format>txt</format> <description>Interface to be passive (i.e. suppress routing updates)</description> </valueHelp> <valueHelp> <format>default</format> <description>Default to suppress routing updates on all interfaces</description> </valueHelp> - <completionHelp> - <list>default</list> - <script>${vyos_completion_dir}/list_interfaces.py</script> - </completionHelp> + <constraint> + <regex>^(br|bond|dum|en|eth|gnv|peth|tun|vti|vxlan|wg|wlan)[0-9]+|lo|default$</regex> + </constraint> <multi/> </properties> </leafNode> <leafNode name="passive-interface-exclude"> <properties> <help>Interface to exclude when using 'passive-interface default'</help> - <valueHelp> - <format><interface></format> - <description>Interface to be passive (i.e. suppress routing updates)</description> - </valueHelp> <completionHelp> <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface to be passive (i.e. suppress routing updates)</description> + </valueHelp> + <constraint> + <regex>^(br|bond|dum|en|eth|gnv|peth|tun|vti|vxlan|wg|wlan)[0-9]+|lo$</regex> + </constraint> <multi/> </properties> </leafNode> @@ -713,6 +721,8 @@ </properties> <children> #include <include/ospf-metric.xml.i> + #include <include/ospf-metric-type.xml.i> + #include <include/ospf-route-map.xml.i> </children> </node> <node name="connected"> @@ -721,6 +731,8 @@ </properties> <children> #include <include/ospf-metric.xml.i> + #include <include/ospf-metric-type.xml.i> + #include <include/ospf-route-map.xml.i> </children> </node> <node name="kernel"> @@ -729,6 +741,8 @@ </properties> <children> #include <include/ospf-metric.xml.i> + #include <include/ospf-metric-type.xml.i> + #include <include/ospf-route-map.xml.i> </children> </node> <node name="rip"> @@ -737,6 +751,8 @@ </properties> <children> #include <include/ospf-metric.xml.i> + #include <include/ospf-metric-type.xml.i> + #include <include/ospf-route-map.xml.i> </children> </node> <node name="static"> @@ -745,6 +761,8 @@ </properties> <children> #include <include/ospf-metric.xml.i> + #include <include/ospf-metric-type.xml.i> + #include <include/ospf-route-map.xml.i> </children> </node> </children> @@ -768,14 +786,7 @@ </leafNode> </children> </node> - <leafNode name="route-map"> - <properties> - <help>Filter routes installed in local route map</help> - <completionHelp> - <path>policy route-map</path> - </completionHelp> - </properties> - </leafNode> + #include <include/ospf-route-map.xml.i> <node name="timers"> <properties> <help>Adjust routing timers</help> @@ -793,39 +804,42 @@ <children> <leafNode name="delay"> <properties> - <help>Delay (msec) from first change received till SPF calculation</help> + <help>Delay from first change received till SPF calculation (default: 200)</help> <valueHelp> <format>u32:0-600000</format> - <description>Delay in msec (default 200)</description> + <description>Delay in milliseconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-600000"/> </constraint> </properties> + <defaultValue>200</defaultValue> </leafNode> <leafNode name="initial-holdtime"> <properties> - <help>Initial hold time(msec) between consecutive SPF calculations</help> + <help>Initial hold time between consecutive SPF calculations (default: 1000)</help> <valueHelp> <format>u32:0-600000</format> - <description>Initial hold time in msec (default 1000)</description> + <description>Initial hold time in milliseconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-600000"/> </constraint> </properties> + <defaultValue>1000</defaultValue> </leafNode> <leafNode name="max-holdtime"> <properties> - <help>Maximum hold time (msec)</help> + <help>Maximum hold time (default: 10000)</help> <valueHelp> <format>u32:0-600000</format> - <description>Max hold time in msec (default 10000)</description> + <description>Max hold time in milliseconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-600000"/> </constraint> </properties> + <defaultValue>10000</defaultValue> </leafNode> </children> </node> diff --git a/interface-definitions/protocols-ospfv3.xml.in b/interface-definitions/protocols-ospfv3.xml.in new file mode 100644 index 000000000..7f80f9f9d --- /dev/null +++ b/interface-definitions/protocols-ospfv3.xml.in @@ -0,0 +1,221 @@ +<?xml version="1.0" encoding="utf-8"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="ospfv3" owner="${vyos_conf_scripts_dir}/protocols_ospfv3.py"> + <properties> + <help>Open Shortest Path First (OSPF) for IPv6</help> + <priority>620</priority> + </properties> + <children> + <tagNode name="area"> + <properties> + <help>OSPFv3 Area</help> + <valueHelp> + <format>u32</format> + <description>Area ID as a decimal value</description> + </valueHelp> + <valueHelp> + <format>ipv4</format> + <description>Area ID in IP address forma</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + <validator name="ip-address"/> + </constraint> + </properties> + <children> + <leafNode name="export-list"> + <properties> + <help>Name of export-list</help> + <completionHelp> + <path>policy access-list6</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="import-list"> + <properties> + <help>Name of import-list</help> + <completionHelp> + <path>policy access-list6</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="interface"> + <properties> + <help>Enable routing on an IPv6 interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface used for routing information exchange</description> + </valueHelp> + <constraint> + <regex>^(br|bond|dum|en|eth|gnv|peth|tun|vti|vxlan|wg|wlan)[0-9]+|lo$</regex> + </constraint> + <multi/> + </properties> + </leafNode> + <tagNode name="range"> + <properties> + <help>Specify IPv6 prefix (border routers only)</help> + <valueHelp> + <format>ipv6net</format> + <description>Specify IPv6 prefix (border routers only)</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + <leafNode name="advertise"> + <properties> + <help>Advertise this range</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="not-advertise"> + <properties> + <help>Do not advertise this range</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </tagNode> + <node name="distance"> + <properties> + <help>Administrative distance</help> + </properties> + <children> + <leafNode name="global"> + <properties> + <help>OSPFv3 administrative distance</help> + <valueHelp> + <format>u32:1-255</format> + <description>Administrative distance</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <node name="ospfv3"> + <properties> + <help>OSPFv3 administrative distance</help> + </properties> + <children> + <leafNode name="external"> + <properties> + <help>Distance for external routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for external routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="inter-area"> + <properties> + <help>Distance for inter-area routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for inter-area routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + <leafNode name="intra-area"> + <properties> + <help>Distance for intra-area routes</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for intra-area routes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="parameters"> + <properties> + <help>OSPFv3 specific parameters</help> + </properties> + <children> + <leafNode name="router-id"> + <properties> + <help>Override the default router identifier</help> + <valueHelp> + <format>ipv4</format> + <description>Override the default router identifier</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="redistribute"> + <properties> + <help>Redistribute information from another routing protocol</help> + </properties> + <children> + <node name="bgp"> + <properties> + <help>Redistribute BGP routes</help> + </properties> + <children> + #include <include/ospf-route-map.xml.i> + </children> + </node> + <node name="connected"> + <properties> + <help>Redistribute connected routes</help> + </properties> + <children> + #include <include/ospf-route-map.xml.i> + </children> + </node> + <node name="kernel"> + <properties> + <help>Redistribute kernel routes</help> + </properties> + <children> + #include <include/ospf-route-map.xml.i> + </children> + </node> + <node name="ripng"> + <properties> + <help>Redistribute RIPNG routes</help> + </properties> + <children> + #include <include/ospf-route-map.xml.i> + </children> + </node> + <node name="static"> + <properties> + <help>Redistribute static routes</help> + </properties> + <children> + #include <include/ospf-route-map.xml.i> + </children> + </node> + </children> + </node> + #include <include/ospf-route-map.xml.i> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in index 07241fcc2..c54de58f5 100644 --- a/interface-definitions/service_ipoe-server.xml.in +++ b/interface-definitions/service_ipoe-server.xml.in @@ -153,7 +153,7 @@ <properties> <help>Client mac address allowed to receive an IP address</help> <valueHelp> - <format>h:h:h:h:h:h</format> + <format>macaddr</format> <description>Hardware (MAC) address</description> </valueHelp> <constraint> diff --git a/op-mode-definitions/show-bridge.xml.in b/op-mode-definitions/show-bridge.xml.in index 8c1f7c398..78c350e44 100644 --- a/op-mode-definitions/show-bridge.xml.in +++ b/op-mode-definitions/show-bridge.xml.in @@ -29,6 +29,12 @@ </properties> <command>/sbin/brctl showstp $3</command> </leafNode> + <leafNode name="fdb"> + <properties> + <help>Show the forwarding database of the bridge</help> + </properties> + <command>/usr/sbin/bridge -c fdb show br $3</command> + </leafNode> </children> </tagNode> </children> diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index bcaec55be..abd91583d 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -307,3 +307,26 @@ def verify_diffie_hellman_length(file, min_keysize): return False +def verify_route_maps(config): + """ + Common helper function used by routing protocol implementations to perform + recurring validation if the specified route-map for either zebra to kernel + installation exists (this is the top-level route_map key) or when a route + is redistributed with a route-map that it exists! + """ + if 'route_map' in config: + route_map = config['route_map'] + # Check if the specified route-map exists, if not error out + if dict_search(f'policy.route_map.{route_map}', config) == None: + raise ConfigError(f'Specified route-map "{route_map}" does not exist!') + + if 'redistribute' in config: + for protocol, protocol_config in config['redistribute'].items(): + if 'route_map' in protocol_config: + # A hyphen in a route-map name will be converted to _, take care + # about this effect during validation + route_map = protocol_config['route_map'].replace('-','_') + # Check if the specified route-map exists, if not error out + if dict_search(f'policy.route_map.{route_map}', config) == None: + raise ConfigError(f'Redistribution route-map "{route_map}" ' \ + f'for "{protocol}" does not exist!') diff --git a/smoketest/configs/dmz-guest-lan-nat-pppoe-router b/smoketest/configs/dmz-guest-lan-nat-pppoe-router index 075f18b9e..e671126a6 100644 --- a/smoketest/configs/dmz-guest-lan-nat-pppoe-router +++ b/smoketest/configs/dmz-guest-lan-nat-pppoe-router @@ -1393,9 +1393,6 @@ service { } system { config-management { - commit-archive { - location tftp://172.16.20.200/CONFIGS/vyos - } commit-revisions 200 } conntrack { diff --git a/smoketest/configs/ospf-config b/smoketest/configs/ospf-config new file mode 100644 index 000000000..fe313e4b0 --- /dev/null +++ b/smoketest/configs/ospf-config @@ -0,0 +1,120 @@ +interfaces { + dummy dum0 { + address 172.18.254.201/32 + } + ethernet eth0 { + duplex auto + smp-affinity auto + speed auto + vif 201 { + address 172.18.201.10/24 + ip { + ospf { + authentication { + md5 { + key-id 10 { + md5-key OSPFVyOSNET + } + } + } + dead-interval 40 + hello-interval 10 + priority 1 + retransmit-interval 5 + transmit-delay 1 + } + } + } + } + ethernet eth1 { + duplex auto + smp-affinity auto + speed auto + } +} +protocols { + ospf { + area 0 { + network 172.18.201.0/24 + network 172.18.254.201/32 + } + log-adjacency-changes { + } + parameters { + abr-type cisco + router-id 172.18.254.201 + } + passive-interface default + passive-interface-exclude eth0.201 + } + static { + route 0.0.0.0/0 { + next-hop 172.18.201.254 { + distance 10 + } + } + } +} +service { + lldp { + interface all { + } + } + snmp { + community public { + authorization ro + network 172.16.100.0/24 + } + contact "VyOS maintainers and contributors <maintainers@vyos.io>" + location "Jenkins" + } + ssh { + disable-host-validation + port 22 + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + level admin + } + } + name-server 172.16.254.30 + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Europe/Berlin +} + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6 */ diff --git a/smoketest/configs/small-as-bgp-vrrp b/smoketest/configs/small-as-bgp-vrrp new file mode 100644 index 000000000..61286c324 --- /dev/null +++ b/smoketest/configs/small-as-bgp-vrrp @@ -0,0 +1,683 @@ +firewall { + all-ping enable + broadcast-ping disable + config-trap disable + group { + address-group NET-VYOS-HTTPS-4 { + address 10.0.150.73 + } + ipv6-network-group NET-VYOS-6 { + network 2001:db8:200::/40 + } + network-group NET-VYOS-4 { + network 10.0.150.0/23 + network 192.168.189.0/24 + } + port-group MY-NAS-PORTS { + port 80 + port 5000 + port 5001 + port 6022 + port 9443 + } + } + ipv6-name WAN-TO-VLAN15-6 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + source { + group { + network-group NET-VYOS-6 + } + } + } + rule 1010 { + action accept + destination { + address 2001:db8:200:15::a + group { + port-group MY-NAS-PORTS + } + } + protocol tcp + } + } + ipv6-receive-redirects disable + ipv6-src-route disable + ip-src-route disable + log-martians enable + name WAN-TO-VLAN15-4 { + default-action drop + enable-default-log + rule 1 { + action accept + state { + established enable + related enable + } + } + rule 2 { + action drop + log enable + state { + invalid enable + } + } + rule 100 { + action accept + source { + group { + network-group NET-VYOS-4 + } + } + } + rule 1000 { + action accept + destination { + group { + address-group NET-VYOS-HTTPS-4 + } + port 80,443 + } + protocol tcp + } + rule 1010 { + action accept + destination { + address 10.0.150.74 + group { + port-group MY-NAS-PORTS + } + } + protocol tcp + } + } + receive-redirects disable + send-redirects enable + source-validation disable + syn-cookies enable + twa-hazards-protection disable +} +high-availability { + vrrp { + group VLAN5-IPv4 { + interface eth0.5 + preempt-delay 180 + priority 250 + virtual-address 10.0.150.120/28 + vrid 5 + } + group VLAN5-IPv6 { + interface eth0.5 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:f0::ffff/64 + vrid 6 + } + group VLAN10-IPv4 { + interface eth0.10 + preempt-delay 180 + priority 250 + virtual-address 10.0.150.62/26 + vrid 10 + } + group VLAN10-IPv6 { + interface eth0.10 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:10::ffff/64 + virtual-address 2001:db8:200::ffff/64 + vrid 11 + } + group VLAN15-IPv4 { + interface eth0.15 + preempt-delay 180 + priority 250 + virtual-address 10.0.150.78/28 + vrid 15 + } + group VLAN15-IPv6 { + interface eth0.15 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:15::ffff/64 + vrid 16 + } + group VLAN500-IPv4 { + interface eth0.500 + preempt-delay 180 + priority 250 + virtual-address 10.0.151.238/28 + vrid 238 + } + group VLAN500-IPv6 { + interface eth0.500 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:50::ffff/64 + vrid 239 + } + group VLAN520-IPv4 { + interface eth0.520 + preempt-delay 180 + priority 250 + virtual-address 10.0.150.190/28 + vrid 52 + } + group VLAN520-IPv6 { + interface eth0.520 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:520::ffff/64 + vrid 53 + } + group VLAN810-IPv4 { + interface eth0.810 + preempt-delay 180 + priority 250 + virtual-address 10.0.151.30/27 + vrid 80 + } + group VLAN810-IPv6 { + interface eth0.810 + preempt-delay 180 + priority 250 + virtual-address 2001:db8:200:102::ffff/64 + vrid 81 + } + sync-group VYOS { + member VLAN5-IPv4 + member VLAN5-IPv6 + member VLAN10-IPv4 + member VLAN10-IPv6 + member VLAN500-IPv4 + member VLAN500-IPv6 + member VLAN15-IPv4 + member VLAN15-IPv6 + member VLAN810-IPv6 + member VLAN810-IPv4 + member VLAN520-IPv4 + member VLAN520-IPv6 + } + } +} +interfaces { + dummy dum0 { + address 2001:db8:200:ffff::2/128 + address 10.0.151.251/32 + } + ethernet eth0 { + vif 5 { + address 10.0.150.121/28 + address 2001:db8:200:f0::4/64 + ip { + ospf { + authentication { + md5 { + key-id 10 { + md5-key vyosospfkey + } + } + } + cost 10 + dead-interval 40 + hello-interval 10 + network broadcast + priority 200 + retransmit-interval 5 + transmit-delay 5 + } + } + } + vif 10 { + address 2001:db8:200:10::1:ffff/64 + address 2001:db8:200::1:ffff/64 + address 10.0.150.60/26 + } + vif 15 { + address 10.0.150.76/28 + address 2001:db8:200:15::1:ffff/64 + firewall { + out { + ipv6-name WAN-TO-VLAN15-6 + name WAN-TO-VLAN15-4 + } + } + } + vif 50 { + address 192.168.189.2/24 + } + vif 110 { + address 2001:db8:200:101::ffff/64 + address 10.0.151.190/27 + address 10.0.151.158/28 + } + vif 410 { + address 10.0.151.206/28 + address 2001:db8:200:104::ffff/64 + } + vif 450 { + address 2001:db8:200:103::ffff/64 + address 10.0.151.142/29 + disable + } + vif 500 { + address 10.0.151.236/28 + address 2001:db8:200:50::1:ffff/64 + } + vif 520 { + address 10.0.150.188/26 + address 2001:db8:200:520::1:ffff/64 + } + vif 800 { + address 2001:db8:200:ff::104:1/112 + address 10.0.151.212/31 + } + vif 810 { + address 10.0.151.28/27 + address 2001:db8:200:102::1:ffff/64 + } + } + ethernet eth1 { + } + loopback lo { + } +} +policy { + prefix-list as65000-origin-v4 { + rule 10 { + action permit + prefix 10.0.150.0/23 + } + rule 100 { + action permit + prefix 0.0.0.0/0 + } + } + prefix-list6 as65000-origin-v6 { + rule 10 { + action permit + prefix 2001:db8:200::/40 + } + } + route-map as65010-in { + rule 10 { + action permit + set { + local-preference 30 + } + } + } + route-map as65010-out { + rule 10 { + action permit + set { + as-path-prepend "65000 65000" + } + } + } +} +protocols { + bgp 65000 { + address-family { + ipv4-unicast { + network 10.0.150.0/23 { + } + } + ipv6-unicast { + network 2001:db8:200::/40 { + } + } + } + neighbor 10.0.151.222 { + address-family { + ipv4-unicast { + default-originate { + } + prefix-list { + export as65000-origin-v4 + } + route-map { + export as65010-out + import as65010-in + } + soft-reconfiguration { + inbound + } + } + } + capability { + dynamic + } + remote-as 65010 + } + neighbor 10.0.151.252 { + peer-group VYOSv4 + } + neighbor 10.0.151.254 { + peer-group VYOSv4 + } + neighbor 2001:db8:200:ffff::3 { + peer-group VYOSv6 + } + neighbor 2001:db8:200:ffff::a { + peer-group VYOSv6 + } + neighbor 2001:db8:200:ff::101:2 { + address-family { + ipv6-unicast { + capability { + dynamic + } + prefix-list { + export as65000-origin-v6 + } + route-map { + import as65010-in + } + soft-reconfiguration { + inbound + } + } + } + remote-as 65010 + } + parameters { + default { + no-ipv4-unicast + } + log-neighbor-changes + router-id 10.0.151.251 + } + peer-group VYOSv4 { + address-family { + ipv4-unicast { + nexthop-self { + } + } + } + capability { + dynamic + } + remote-as 65000 + update-source dum0 + } + peer-group VYOSv6 { + address-family { + ipv6-unicast { + nexthop-self { + } + } + } + capability { + dynamic + } + remote-as 65000 + update-source dum0 + } + timers { + holdtime 30 + keepalive 10 + } + } + ospf { + area 0 { + area-type { + normal + } + authentication md5 + network 10.0.151.251/32 + network 10.0.151.208/31 + network 10.0.150.112/28 + } + parameters { + abr-type cisco + router-id 10.0.151.251 + } + passive-interface default + passive-interface-exclude dum0 + passive-interface-exclude eth0.5 + redistribute { + connected { + metric-type 2 + } + static { + metric-type 2 + } + } + } + ospfv3 { + area 0.0.0.0 { + interface dum0 + interface eth0.5 + } + parameters { + router-id 10.0.151.251 + } + redistribute { + connected { + } + static { + } + } + } + static { + route 10.0.0.0/8 { + MY-NAS { + distance 254 + } + } + route 172.16.0.0/12 { + MY-NAS { + distance 254 + } + } + route 192.168.0.0/16 { + MY-NAS { + distance 254 + } + } + route 193.148.249.144/32 { + next-hop 192.168.189.1 { + } + } + route 10.0.150.0/23 { + MY-NAS { + distance 254 + } + } + route 10.0.151.32/27 { + next-hop 10.0.151.5 { + } + } + route6 2001:db8:2fe:ffff::/64 { + next-hop 2001:db8:200:102::4 { + } + } + route6 2001:db8:2ff::/48 { + next-hop 2001:db8:200:101::1 { + } + } + route6 2001:db8:200::/40 { + MY-NAS { + distance 254 + } + } + } +} +service { + dhcp-server { + shared-network-name NET-VYOS-DHCP-1 { + subnet 10.0.151.224/28 { + default-router 10.0.151.238 + dns-server 10.0.150.2 + dns-server 10.0.150.1 + domain-name vyos.net + failover { + local-address 10.0.151.236 + name NET-VYOS-DHCP-1 + peer-address 10.0.151.237 + status primary + } + lease 1800 + range 0 { + start 10.0.151.225 + stop 10.0.151.237 + } + } + } + shared-network-name NET-VYOS-HOSTING-1 { + subnet 10.0.150.128/26 { + default-router 10.0.150.190 + dns-server 10.0.150.2 + dns-server 10.0.150.1 + domain-name vyos.net + failover { + local-address 10.0.150.188 + name NET-VYOS-HOSTING-1 + peer-address 10.0.150.189 + status primary + } + lease 604800 + range 0 { + start 10.0.150.129 + stop 10.0.150.187 + } + } + } + } + lldp { + interface all { + } + management-address 10.0.151.251 + snmp { + enable + } + } + router-advert { + interface eth4.500 { + default-preference high + name-server 2001:db8:200::1 + name-server 2001:db8:200::2 + prefix 2001:db8:200:50::/64 { + valid-lifetime infinity + } + } + interface eth4.520 { + default-preference high + name-server 2001:db8:200::1 + name-server 2001:db8:200::2 + prefix 2001:db8:200:520::/64 { + valid-lifetime infinity + } + } + } + snmp { + community public { + network 10.0.150.0/26 + network 2001:db8:200:10::/64 + } + contact noc@vyos.net + listen-address 10.0.151.251 { + } + listen-address 2001:db8:200:ffff::2 { + } + location "Jenkins" + } + ssh { + disable-host-validation + listen-address 10.0.151.251 + listen-address 2001:db8:200:ffff::2 + listen-address 192.168.189.2 + loglevel fatal + port 22 + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + banner { + pre-login "VyOS - Network\n" + } + radius { + server 192.0.2.1 { + key SuperS3cretRADIUSkey + timeout 1 + } + server 192.0.2.2 { + key SuperS3cretRADIUSkey + timeout 1 + } + source-address 192.0.2.254 + } + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.0.2.1 + name-server 192.0.2.2 + name-server 2001:db8:200::1 + name-server 2001:db8:200::2 + ntp { + allow-clients { + address 10.0.150.0/23 + address 2001:db8:200::/40 + } + listen-address 10.0.151.251 + listen-address 2001:db8:200:ffff::2 + server 0.de.pool.ntp.org { + } + server 1.de.pool.ntp.org { + } + server 2.de.pool.ntp.org { + } + } + syslog { + global { + facility all { + level notice + } + facility protocols { + level debug + } + } + host 10.0.150.26 { + facility all { + level all + } + } + } + time-zone Europe/Berlin +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@18:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3-beta-202101151942 diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py new file mode 100755 index 000000000..ccc4196e0 --- /dev/null +++ b/smoketest/scripts/cli/test_nat66.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import jmespath +import json +import unittest + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.util import cmd +from vyos.util import dict_search + +base_path = ['nat66'] +src_path = base_path + ['source'] +dst_path = base_path + ['destination'] + +class TestNAT66(unittest.TestCase): + def setUp(self): + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + self.session = ConfigSession(os.getpid()) + self.session.delete(base_path) + + def tearDown(self): + self.session.delete(base_path) + self.session.commit() + + def test_source_nat66(self): + source_prefix = 'fc00::/64' + translation_prefix = 'fc01::/64' + self.session.set(src_path + ['rule', '1', 'outbound-interface', 'eth1']) + self.session.set(src_path + ['rule', '1', 'source', 'prefix', source_prefix]) + self.session.set(src_path + ['rule', '1', 'translation', 'prefix', translation_prefix]) + + # check validate() - outbound-interface must be defined + self.session.commit() + + tmp = cmd('sudo nft -j list table ip6 nat') + data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) + + for idx in range(0, len(data_json)): + data = data_json[idx] + + self.assertEqual(data['chain'], 'POSTROUTING') + self.assertEqual(data['family'], 'ip6') + self.assertEqual(data['table'], 'nat') + + iface = dict_search('match.right', data['expr'][0]) + address = dict_search('match.right.prefix.addr', data['expr'][2]) + mask = dict_search('match.right.prefix.len', data['expr'][2]) + translation_address = dict_search('snat.addr.prefix.addr', data['expr'][3]) + translation_mask = dict_search('snat.addr.prefix.len', data['expr'][3]) + + self.assertEqual(iface, 'eth1') + # check for translation address + self.assertEqual(f'{translation_address}/{translation_mask}', translation_prefix) + self.assertEqual(f'{address}/{mask}', source_prefix) + + def test_destination_nat66(self): + destination_address = 'fc00::1' + translation_address = 'fc01::1' + self.session.set(dst_path + ['rule', '1', 'inbound-interface', 'eth1']) + self.session.set(dst_path + ['rule', '1', 'destination', 'address', destination_address]) + self.session.set(dst_path + ['rule', '1', 'translation', 'address', translation_address]) + + # check validate() - outbound-interface must be defined + self.session.commit() + + tmp = cmd('sudo nft -j list table ip6 nat') + data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) + + for idx in range(0, len(data_json)): + data = data_json[idx] + + self.assertEqual(data['chain'], 'PREROUTING') + self.assertEqual(data['family'], 'ip6') + self.assertEqual(data['table'], 'nat') + + iface = dict_search('match.right', data['expr'][0]) + dnat_addr = dict_search('dnat.addr', data['expr'][3]) + + self.assertEqual(dnat_addr, translation_address) + self.assertEqual(iface, 'eth1') + + def test_destination_nat66_prefix(self): + destination_prefix = 'fc00::/64' + translation_prefix = 'fc01::/64' + self.session.set(dst_path + ['rule', '1', 'inbound-interface', 'eth1']) + self.session.set(dst_path + ['rule', '1', 'destination', 'address', destination_prefix]) + self.session.set(dst_path + ['rule', '1', 'translation', 'address', translation_prefix]) + + # check validate() - outbound-interface must be defined + self.session.commit() + + tmp = cmd('sudo nft -j list table ip6 nat') + data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) + + for idx in range(0, len(data_json)): + data = data_json[idx] + + self.assertEqual(data['chain'], 'PREROUTING') + self.assertEqual(data['family'], 'ip6') + self.assertEqual(data['table'], 'nat') + + iface = dict_search('match.right', data['expr'][0]) + translation_address = dict_search('dnat.addr.prefix.addr', data['expr'][3]) + translation_mask = dict_search('dnat.addr.prefix.len', data['expr'][3]) + + self.assertEqual(f'{translation_address}/{translation_mask}', translation_prefix) + self.assertEqual(iface, 'eth1') + + def test_source_nat66_required_translation_prefix(self): + # T2813: Ensure translation address is specified + rule = '5' + source_prefix = 'fc00::/64' + translation_prefix = 'fc00:2::/64' + self.session.set(src_path + ['rule', rule, 'source', 'prefix', source_prefix]) + + # check validate() - outbound-interface must be defined + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(src_path + ['rule', rule, 'outbound-interface', 'eth0']) + + # check validate() - translation address not specified + with self.assertRaises(ConfigSessionError): + self.session.commit() + + self.session.set(src_path + ['rule', rule, 'translation', 'prefix', translation_prefix]) + self.session.commit() + + def test_nat66_no_rules(self): + # T3206: deleting all rules but keep the direction 'destination' or + # 'source' resulteds in KeyError: 'rule'. + # + # Test that both 'nat destination' and 'nat source' nodes can exist + # without any rule + self.session.set(src_path) + self.session.set(dst_path) + self.session.commit() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index fef32741e..66ca3cdd7 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -26,59 +26,72 @@ PROCESS_NAME = 'bgpd' ASN = '64512' base_path = ['protocols', 'bgp', ASN] +route_map_in = 'foo-map-in' +route_map_out = 'foo-map-out' +prefix_list_in = 'pfx-foo-in' +prefix_list_out = 'pfx-foo-out' + neighbor_config = { '192.0.2.1' : { - 'cap_dynamic' : '', - 'cap_ext_next': '', - 'remote_as' : '100', - 'adv_interv' : '400', - 'passive' : '', - 'password' : 'VyOS-Secure123', - 'shutdown' : '', - 'cap_over' : '', - 'ttl_security': '5', - 'local_as' : '300', + 'cap_dynamic' : '', + 'cap_ext_next' : '', + 'remote_as' : '100', + 'adv_interv' : '400', + 'passive' : '', + 'password' : 'VyOS-Secure123', + 'shutdown' : '', + 'cap_over' : '', + 'ttl_security' : '5', + 'local_as' : '300', + 'route_map_in' : route_map_in, + 'route_map_out': route_map_out, }, '192.0.2.2' : { - 'remote_as' : '200', - 'shutdown' : '', - 'no_cap_nego' : '', - 'port' : '667', - 'cap_strict' : '', + 'remote_as' : '200', + 'shutdown' : '', + 'no_cap_nego' : '', + 'port' : '667', + 'cap_strict' : '', + 'pfx_list_in' : prefix_list_in, + 'pfx_list_out' : prefix_list_out, }, '192.0.2.3' : { - 'description' : 'foo bar baz', - 'remote_as' : '200', - 'passive' : '', - 'multi_hop' : '5', - 'update_src' : 'lo', + 'description' : 'foo bar baz', + 'remote_as' : '200', + 'passive' : '', + 'multi_hop' : '5', + 'update_src' : 'lo', }, } peer_group_config = { 'foo' : { - 'remote_as' : '100', - 'passive' : '', - 'password' : 'VyOS-Secure123', - 'shutdown' : '', - 'cap_over' : '', + 'remote_as' : '100', + 'passive' : '', + 'password' : 'VyOS-Secure123', + 'shutdown' : '', + 'cap_over' : '', # XXX: not available in current Perl backend # 'ttl_security': '5', }, 'bar' : { - 'description' : 'foo peer bar group', - 'remote_as' : '200', - 'shutdown' : '', - 'no_cap_nego' : '', - 'local_as' : '300', + 'description' : 'foo peer bar group', + 'remote_as' : '200', + 'shutdown' : '', + 'no_cap_nego' : '', + 'local_as' : '300', + 'pfx_list_in' : prefix_list_in, + 'pfx_list_out' : prefix_list_out, }, 'baz' : { - 'cap_dynamic' : '', - 'cap_ext_next': '', - 'remote_as' : '200', - 'passive' : '', - 'multi_hop' : '5', - 'update_src' : 'lo', + 'cap_dynamic' : '', + 'cap_ext_next' : '', + 'remote_as' : '200', + 'passive' : '', + 'multi_hop' : '5', + 'update_src' : 'lo', + 'route_map_in' : route_map_in, + 'route_map_out': route_map_out, }, } @@ -89,7 +102,19 @@ class TestProtocolsBGP(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) + self.session.set(['policy', 'route-map', route_map_in, 'rule', '10', 'action', 'permit']) + self.session.set(['policy', 'route-map', route_map_out, 'rule', '10', 'action', 'permit']) + self.session.set(['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'action', 'permit']) + self.session.set(['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'prefix', '192.0.2.0/25']) + self.session.set(['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'action', 'permit']) + self.session.set(['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'prefix', '192.0.2.128/25']) + def tearDown(self): + self.session.delete(['policy', 'route-map', route_map_in]) + self.session.delete(['policy', 'route-map', route_map_out]) + self.session.delete(['policy', 'prefix-list', prefix_list_in]) + self.session.delete(['policy', 'prefix-list', prefix_list_out]) + self.session.delete(base_path) self.session.commit() del self.session @@ -122,6 +147,15 @@ class TestProtocolsBGP(unittest.TestCase): self.assertIn(f' neighbor {peer} ttl-security hops {peer_config["ttl_security"]}', frrconfig) if 'update_src' in peer_config: self.assertIn(f' neighbor {peer} update-source {peer_config["update_src"]}', frrconfig) + if 'route_map_in' in peer_config: + self.assertIn(f' neighbor {peer} route-map {peer_config["route_map_in"]} in', frrconfig) + if 'route_map_out' in peer_config: + self.assertIn(f' neighbor {peer} route-map {peer_config["route_map_out"]} out', frrconfig) + if 'pfx_list_in' in peer_config: + self.assertIn(f' neighbor {peer} prefix-list {peer_config["pfx_list_in"]} in', frrconfig) + if 'pfx_list_out' in peer_config: + self.assertIn(f' neighbor {peer} prefix-list {peer_config["pfx_list_out"]} out', frrconfig) + def test_bgp_01_simple(self): router_id = '127.0.0.1' @@ -184,6 +218,14 @@ class TestProtocolsBGP(unittest.TestCase): self.session.set(base_path + ['neighbor', neighbor, 'ttl-security', 'hops', config["ttl_security"]]) if 'update_src' in config: self.session.set(base_path + ['neighbor', neighbor, 'update-source', config["update_src"]]) + if 'route_map_in' in config: + self.session.set(base_path + ['neighbor', neighbor, 'address-family', 'ipv4-unicast', 'route-map', 'import', config["route_map_in"]]) + if 'route_map_out' in config: + self.session.set(base_path + ['neighbor', neighbor, 'address-family', 'ipv4-unicast', 'route-map', 'export', config["route_map_out"]]) + if 'pfx_list_in' in config: + self.session.set(base_path + ['neighbor', neighbor, 'address-family', 'ipv4-unicast', 'prefix-list', 'import', config["pfx_list_in"]]) + if 'pfx_list_out' in config: + self.session.set(base_path + ['neighbor', neighbor, 'address-family', 'ipv4-unicast', 'prefix-list', 'export', config["pfx_list_out"]]) # commit changes self.session.commit() @@ -231,6 +273,14 @@ class TestProtocolsBGP(unittest.TestCase): self.session.set(base_path + ['peer-group', peer_group, 'ttl-security', 'hops', config["ttl_security"]]) if 'update_src' in config: self.session.set(base_path + ['peer-group', peer_group, 'update-source', config["update_src"]]) + if 'route_map_in' in config: + self.session.set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'route-map', 'import', config["route_map_in"]]) + if 'route_map_out' in config: + self.session.set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'route-map', 'export', config["route_map_out"]]) + if 'pfx_list_in' in config: + self.session.set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'prefix-list', 'import', config["pfx_list_in"]]) + if 'pfx_list_out' in config: + self.session.set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'prefix-list', 'export', config["pfx_list_out"]]) # commit changes self.session.commit() diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py new file mode 100755 index 000000000..d47838d8c --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from vyos.configsession import ConfigSession +from vyos.ifconfig import Section +from vyos.util import cmd +from vyos.util import process_named_running + +PROCESS_NAME = 'ospfd' +base_path = ['protocols', 'ospf'] + +route_map = 'foo-bar-baz10' + +def getFRROSPFconfig(): + return cmd('vtysh -c "show run" | sed -n "/router ospf/,/^!/p"') + +class TestProtocolsOSPF(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + self.session.set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + self.session.set(['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit']) + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.session.delete(['policy', 'route-map', route_map]) + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_ospf_01_defaults(self): + # commit changes + self.session.set(base_path) + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + + + def test_ospf_02_simple(self): + router_id = '127.0.0.1' + abr_type = 'ibm' + bandwidth = '1000' + metric = '123' + + self.session.set(base_path + ['auto-cost', 'reference-bandwidth', bandwidth]) + self.session.set(base_path + ['parameters', 'router-id', router_id]) + self.session.set(base_path + ['parameters', 'abr-type', abr_type]) + self.session.set(base_path + ['log-adjacency-changes', 'detail']) + self.session.set(base_path + ['default-metric', metric]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth {bandwidth}', frrconfig) + self.assertIn(f' ospf router-id {router_id}', frrconfig) + self.assertIn(f' ospf abr-type {abr_type}', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + self.assertIn(f' default-metric {metric}', frrconfig) + + + def test_ospf_03_access_list(self): + acl = '100' + seq = '10' + protocols = ['bgp', 'connected', 'kernel', 'rip', 'static'] + + self.session.set(['policy', 'access-list', acl, 'rule', seq, 'action', 'permit']) + self.session.set(['policy', 'access-list', acl, 'rule', seq, 'source', 'any']) + self.session.set(['policy', 'access-list', acl, 'rule', seq, 'destination', 'any']) + for ptotocol in protocols: + self.session.set(base_path + ['access-list', acl, 'export', ptotocol]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + for ptotocol in protocols: + self.assertIn(f' distribute-list {acl} out {ptotocol}', frrconfig) # defaults + self.session.delete(['policy', 'access-list', acl]) + + + def test_ospf_04_default_originate(self): + seq = '100' + metric = '50' + metric_type = '1' + + self.session.set(base_path + ['default-information', 'originate', 'metric', metric]) + self.session.set(base_path + ['default-information', 'originate', 'metric-type', metric_type]) + self.session.set(base_path + ['default-information', 'originate', 'route-map', route_map]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + # Now set 'always' + self.session.set(base_path + ['default-information', 'originate', 'always']) + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + + def test_ospf_05_options(self): + global_distance = '128' + intra_area = '100' + inter_area = '110' + external = '120' + on_startup = '30' + on_shutdown = '60' + refresh = '50' + + self.session.set(base_path + ['distance', 'global', global_distance]) + self.session.set(base_path + ['distance', 'ospf', 'external', external]) + self.session.set(base_path + ['distance', 'ospf', 'intra-area', intra_area]) + + self.session.set(base_path + ['max-metric', 'router-lsa', 'on-startup', on_startup]) + self.session.set(base_path + ['max-metric', 'router-lsa', 'on-shutdown', on_shutdown]) + + self.session.set(base_path + ['mpls-te', 'enable']) + self.session.set(base_path + ['refresh', 'timers', refresh]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' mpls-te on', frrconfig) + self.assertIn(f' mpls-te router-address 0.0.0.0', frrconfig) # default + self.assertIn(f' distance {global_distance}', frrconfig) + self.assertIn(f' distance ospf intra-area {intra_area} external {external}', frrconfig) + self.assertIn(f' max-metric router-lsa on-startup {on_startup}', frrconfig) + self.assertIn(f' max-metric router-lsa on-shutdown {on_shutdown}', frrconfig) + self.assertIn(f' refresh timer {refresh}', frrconfig) + + + # enable inter-area + self.session.set(base_path + ['distance', 'ospf', 'inter-area', inter_area]) + self.session.commit() + + frrconfig = getFRROSPFconfig() + self.assertIn(f' distance ospf intra-area {intra_area} inter-area {inter_area} external {external}', frrconfig) + + + def test_ospf_06_neighbor(self): + priority = '10' + poll_interval = '20' + neighbors = ['1.1.1.1', '2.2.2.2', '3.3.3.3'] + for neighbor in neighbors: + self.session.set(base_path + ['neighbor', neighbor, 'priority', priority]) + self.session.set(base_path + ['neighbor', neighbor, 'poll-interval', poll_interval]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + for neighbor in neighbors: + self.assertIn(f' neighbor {neighbor} priority {priority} poll-interval {poll_interval}', frrconfig) # default + + + def test_ospf_07_passive_interface(self): + self.session.set(base_path + ['passive-interface', 'default']) + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.session.set(base_path + ['passive-interface-exclude', interface]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) # default + for interface in interfaces: + self.assertIn(f' no passive-interface {interface}', frrconfig) # default + + + def test_ospf_08_redistribute(self): + metric = '15' + metric_type = '1' + redistribute = ['bgp', 'connected', 'kernel', 'rip', 'static'] + + for protocol in redistribute: + self.session.set(base_path + ['redistribute', protocol, 'metric', metric]) + self.session.set(base_path + ['redistribute', protocol, 'route-map', route_map]) + if protocol not in ['kernel', 'static']: + self.session.set(base_path + ['redistribute', protocol, 'metric-type', metric_type]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + for protocol in redistribute: + if protocol in ['kernel', 'static']: + self.assertIn(f' redistribute {protocol} metric {metric} route-map {route_map}', frrconfig) + else: + self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + + def test_ospf_09_area(self): + area = '0' + networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] + for network in networks: + self.session.set(base_path + ['area', area, 'network', network]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + for network in networks: + self.assertIn(f' network {network} area {area}', frrconfig) + + + def test_ospf_10_virtual_link(self): + area = '10' + shortcut = 'enable' + virtual_link = '192.0.2.1' + hello = '6' + retransmit = '5' + transmit = '5' + dead = '40' + + self.session.set(base_path + ['area', area, 'shortcut', shortcut]) + self.session.set(base_path + ['area', area, 'virtual-link', virtual_link, 'hello-interval', hello]) + self.session.set(base_path + ['area', area, 'virtual-link', virtual_link, 'retransmit-interval', retransmit]) + self.session.set(base_path + ['area', area, 'virtual-link', virtual_link, 'transmit-delay', transmit]) + self.session.set(base_path + ['area', area, 'virtual-link', virtual_link, 'dead-interval', dead]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' area {area} shortcut {shortcut}', frrconfig) + self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} transmit-delay {transmit} dead-interval {dead}', frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py new file mode 100755 index 000000000..297d5d996 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from vyos.configsession import ConfigSession +from vyos.ifconfig import Section +from vyos.util import cmd +from vyos.util import process_named_running + +PROCESS_NAME = 'ospf6d' +base_path = ['protocols', 'ospfv3'] + + +def getFRROSPFconfig(): + return cmd('vtysh -c "show run" | sed -n "/router ospf6/,/^!/p"') + +class TestProtocolsOSPFv3(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.session.delete(base_path) + self.session.commit() + del self.session + + + def test_ospfv3_01_basic(self): + area = '0' + seq = '10' + prefix = '2001:db8::/32' + acl_name = 'foo-acl-100' + router_id = '192.0.2.1' + + self.session.set(['policy', 'access-list6', acl_name, 'rule', seq, 'action', 'permit']) + self.session.set(['policy', 'access-list6', acl_name, 'rule', seq, 'source', 'any']) + + self.session.set(base_path + ['parameters', 'router-id', router_id]) + self.session.set(base_path + ['area', area, 'range', prefix, 'advertise']) + self.session.set(base_path + ['area', area, 'export-list', acl_name]) + self.session.set(base_path + ['area', area, 'import-list', acl_name]) + + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.session.set(base_path + ['area', area, 'interface', interface]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' area {area} range {prefix}', frrconfig) + self.assertIn(f' ospf6 router-id {router_id}', frrconfig) + self.assertIn(f' area {area} import-list {acl_name}', frrconfig) + self.assertIn(f' area {area} export-list {acl_name}', frrconfig) + + for interface in interfaces: + self.assertIn(f' interface {interface} area {area}', frrconfig) + + self.session.delete(['policy', 'access-list6', acl_name]) + + + def test_ospfv3_02_distance(self): + dist_global = '200' + dist_external = '110' + dist_inter_area = '120' + dist_intra_area = '130' + + self.session.set(base_path + ['distance', 'global', dist_global]) + self.session.set(base_path + ['distance', 'ospfv3', 'external', dist_external]) + self.session.set(base_path + ['distance', 'ospfv3', 'inter-area', dist_inter_area]) + self.session.set(base_path + ['distance', 'ospfv3', 'intra-area', dist_intra_area]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' distance {dist_global}', frrconfig) + self.assertIn(f' distance ospf6 intra-area {dist_intra_area} inter-area {dist_inter_area} external {dist_external}', frrconfig) + + + def test_ospfv3_03_redistribute(self): + route_map = 'foo-bar' + route_map_seq = '10' + redistribute = ['bgp', 'connected', 'kernel', 'ripng', 'static'] + + self.session.set(['policy', 'route-map', route_map, 'rule', route_map_seq, 'action', 'permit']) + + for protocol in redistribute: + self.session.set(base_path + ['redistribute', protocol, 'route-map', route_map]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf6', frrconfig) + for protocol in redistribute: + self.assertIn(f' redistribute {protocol} route-map {route_map}', frrconfig) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py new file mode 100755 index 000000000..dc3cd550c --- /dev/null +++ b/src/conf_mode/nat66.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 jmespath +import json +import os + +from sys import exit +from netifaces import interfaces + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.template import render +from vyos.util import cmd +from vyos.util import check_kmod +from vyos.util import dict_search +from vyos.template import is_ipv6 +from vyos.xml import defaults +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +k_mod = ['nft_nat', 'nft_chain_nat'] + +iptables_nat_config = '/tmp/vyos-nat66-rules.nft' +ndppd_config = '/run/ndppd/ndppd.conf' + +def get_handler(json, chain, target): + """ Get nftable rule handler number of given chain/target combination. + Handler is required when adding NAT66/Conntrack helper targets """ + for x in json: + if x['chain'] != chain: + continue + if x['target'] != target: + continue + return x['handle'] + + return None + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['nat66'] + nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # T2665: we must add the tagNode defaults individually until this is + # moved to the base class + for direction in ['source', 'destination']: + if direction in nat: + default_values = defaults(base + [direction, 'rule']) + if 'rule' in nat[direction]: + for rule in nat[direction]['rule']: + nat[direction]['rule'][rule] = dict_merge(default_values, + nat[direction]['rule'][rule]) + + # read in current nftable (once) for further processing + tmp = cmd('nft -j list table ip6 raw') + nftable_json = json.loads(tmp) + + # condense the full JSON table into a list with only relevand informations + pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}' + condensed_json = jmespath.search(pattern, nftable_json) + + if not conf.exists(base): + nat['helper_functions'] = 'remove' + nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK') + nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT','NAT_CONNTRACK') + nat['deleted'] = '' + return nat + + # check if NAT66 connection tracking helpers need to be set up - this has to + # be done only once + if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'): + nat['helper_functions'] = 'add' + + # Retrieve current table handler positions + nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK') + nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT','VYATTA_CT_OUTPUT_HOOK') + else: + nat['helper_functions'] = 'has' + + return nat + +def verify(nat): + if not nat or 'deleted' in nat: + # no need to verify the CLI as NAT66 is going to be deactivated + return None + + if 'helper_functions' in nat and nat['helper_functions'] != 'has': + if not (nat['pre_ct_conntrack'] or nat['out_ct_conntrack']): + raise Exception('could not determine nftable ruleset handlers') + + if dict_search('source.rule', nat): + for rule, config in dict_search('source.rule', nat).items(): + err_msg = f'Source NAT66 configuration error in rule {rule}:' + if 'outbound_interface' not in config: + raise ConfigError(f'{err_msg}\n' \ + 'outbound-interface not specified') + else: + if config['outbound_interface'] not in interfaces(): + print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') + + prefix = dict_search('translation.prefix', config) + if prefix != None: + if not is_ipv6(prefix): + raise ConfigError(f'Warning: IPv6 prefix {prefix} is not a valid address prefix') + + prefix = dict_search('source.prefix', config) + if prefix != None: + if not is_ipv6(prefix): + raise ConfigError(f'{err_msg} source-prefix not specified') + + if dict_search('destination.rule', nat): + for rule, config in dict_search('destination.rule', nat).items(): + err_msg = f'Destination NAT66 configuration error in rule {rule}:' + + if 'inbound_interface' not in config: + raise ConfigError(f'{err_msg}\n' \ + 'inbound-interface not specified') + else: + if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): + print(f'WARNING: rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') + + return None + +def generate(nat): + render(iptables_nat_config, 'firewall/nftables-nat66.tmpl', nat, permission=0o755) + render(ndppd_config, 'proxy-ndp/ndppd.conf.tmpl', nat, permission=0o755) + return None + +def apply(nat): + if not nat: + return None + cmd(f'{iptables_nat_config}') + if 'deleted' in nat or not dict_search('source.rule', nat): + cmd('systemctl stop ndppd') + if os.path.isfile(ndppd_config): + os.unlink(ndppd_config) + else: + cmd('systemctl restart ndppd') + if os.path.isfile(iptables_nat_config): + os.unlink(iptables_nat_config) + + return None + +if __name__ == '__main__': + try: + check_kmod(k_mod) + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 73c244571..7c0ffbd27 100644..100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -20,16 +20,19 @@ from sys import exit from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configverify import verify_route_maps from vyos.template import render from vyos.template import render_to_string from vyos.util import call from vyos.util import dict_search +from vyos.xml import defaults from vyos import ConfigError from vyos import frr from vyos import airbag airbag.enable() config_file = r'/tmp/ospf.frr' +frr_daemon = 'ospfd' DEBUG = os.path.exists('/tmp/ospf.debug') if DEBUG: @@ -39,16 +42,72 @@ if DEBUG: ch = logging.StreamHandler() lg.addHandler(ch) -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['protocols', 'ospf'] ospf = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # Bail out early if configuration tree does not exist + if not conf.exists(base): + return ospf + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = defaults(base) + + # We have to cleanup the default dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: default-information + # originate comes with a default metric-type of 2, which will enable the + # entire default-information originate tree, even when not set via CLI so we + # need to check this first and probably drop that key. + if dict_search('default_information.originate', ospf) is None: + del default_values['default_information'] + if dict_search('area.area_type.nssa', ospf) is None: + del default_values['area']['area_type']['nssa'] + if 'mpls_te' not in ospf: + del default_values['mpls_te'] + for protocol in ['bgp', 'connected', 'kernel', 'rip', 'static']: + if dict_search(f'redistribute.{protocol}', ospf) is None: + del default_values['redistribute'][protocol] + # XXX: T2665: we currently have no nice way for defaults under tag nodes, + # clean them out and add them manually :( + del default_values['neighbor'] + del default_values['area']['virtual_link'] + + # merge in remaining default values + ospf = dict_merge(default_values, ospf) + + if 'neighbor' in ospf: + default_values = defaults(base + ['neighbor']) + for neighbor in ospf['neighbor']: + ospf['neighbor'][neighbor] = dict_merge(default_values, ospf['neighbor'][neighbor]) + + if 'area' in ospf: + default_values = defaults(base + ['area', 'virtual-link']) + for area, area_config in ospf['area'].items(): + if 'virtual_link' in area_config: + print(default_values) + for virtual_link in area_config['virtual_link']: + ospf['area'][area]['virtual_link'][virtual_link] = dict_merge( + default_values, ospf['area'][area]['virtual_link'][virtual_link]) + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify() + base = ['policy'] + tmp = conf.get_config_dict(base, key_mangling=('-', '_')) + # Merge policy dict into OSPF dict + ospf = dict_merge(tmp, ospf) + return ospf def verify(ospf): if not ospf: return None + verify_route_maps(ospf) return None def generate(ospf): @@ -65,8 +124,9 @@ def generate(ospf): def apply(ospf): # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(daemon='ospfd') - frr_cfg.modify_section(f'router ospf', '') + frr_cfg.load_configuration(frr_daemon) + frr_cfg.modify_section('router ospf', '') + frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospf['new_frr_config']) # Debugging if DEBUG: @@ -82,13 +142,13 @@ def apply(ospf): print(f'Modified config:\n') print(f'{frr_cfg}') - frr_cfg.commit_configuration(daemon='ospfd') + frr_cfg.commit_configuration(frr_daemon) # If FRR config is blank, rerun the blank commit x times due to frr-reload # behavior/bug not properly clearing out on one commit. if ospf['new_frr_config'] == '': for a in range(5): - frr_cfg.commit_configuration(daemon='ospfd') + frr_cfg.commit_configuration(frr_daemon) return None diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py new file mode 100755 index 000000000..2fb600056 --- /dev/null +++ b/src/conf_mode/protocols_ospfv3.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configverify import verify_route_maps +from vyos.template import render +from vyos.template import render_to_string +from vyos.util import call +from vyos.util import dict_search +from vyos.xml import defaults +from vyos import ConfigError +from vyos import frr +from vyos import airbag +airbag.enable() + +config_file = r'/tmp/ospfv3.frr' +frr_daemon = 'ospf6d' + +DEBUG = os.path.exists('/tmp/ospfv3.debug') +if DEBUG: + import logging + lg = logging.getLogger("vyos.frr") + lg.setLevel(logging.DEBUG) + ch = logging.StreamHandler() + lg.addHandler(ch) + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'ospfv3'] + ospfv3 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # Bail out early if configuration tree does not exist + if not conf.exists(base): + return ospfv3 + + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify() + base = ['policy'] + tmp = conf.get_config_dict(base, key_mangling=('-', '_')) + # Merge policy dict into OSPF dict + ospfv3 = dict_merge(tmp, ospfv3) + + return ospfv3 + +def verify(ospfv3): + if not ospfv3: + return None + + verify_route_maps(ospfv3) + return None + +def generate(ospfv3): + if not ospfv3: + ospfv3['new_frr_config'] = '' + return None + + # render(config) not needed, its only for debug + render(config_file, 'frr/ospfv3.frr.tmpl', ospfv3) + ospfv3['new_frr_config'] = render_to_string('frr/ospfv3.frr.tmpl', ospfv3) + + return None + +def apply(ospfv3): + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + frr_cfg.load_configuration(frr_daemon) + frr_cfg.modify_section('router ospf6', '') + frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospfv3['new_frr_config']) + + # Debugging + if DEBUG: + from pprint import pprint + print('') + print('--------- DEBUGGING ----------') + pprint(dir(frr_cfg)) + print('Existing config:\n') + for line in frr_cfg.original_config: + print(line) + print(f'Replacement config:\n') + print(f'{ospfv3["new_frr_config"]}') + print(f'Modified config:\n') + print(f'{frr_cfg}') + + frr_cfg.commit_configuration(frr_daemon) + + # If FRR config is blank, re-run the blank commit x times due to frr-reload + # behavior/bug not properly clearing out on one commit. + if ospfv3['new_frr_config'] == '': + for a in range(5): + frr_cfg.commit_configuration(frr_daemon) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/migration-scripts/nat66/0-to-1 b/src/migration-scripts/nat66/0-to-1 new file mode 100755 index 000000000..74d64c07b --- /dev/null +++ b/src/migration-scripts/nat66/0-to-1 @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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/>. + +from sys import argv,exit +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +def merge_npt(config,base,rule): + merge_base = ['nat66','source','rule',rule] + # Configure migration functions + if config.exists(base + ['description']): + tmp = config.return_value(base + ['description']) + config.set(merge_base + ['description'],value=tmp) + + if config.exists(base + ['disable']): + tmp = config.return_value(base + ['disable']) + config.set(merge_base + ['disable'],value=tmp) + + if config.exists(base + ['outbound-interface']): + tmp = config.return_value(base + ['outbound-interface']) + config.set(merge_base + ['outbound-interface'],value=tmp) + + if config.exists(base + ['source','prefix']): + tmp = config.return_value(base + ['source','prefix']) + config.set(merge_base + ['source','prefix'],value=tmp) + + if config.exists(base + ['translation','prefix']): + tmp = config.return_value(base + ['translation','prefix']) + config.set(merge_base + ['translation','prefix'],value=tmp) + +if not config.exists(['nat', 'nptv6']): + # Nothing to do + exit(0) + +for rule in config.list_nodes(['nat', 'nptv6', 'rule']): + base = ['nat', 'nptv6', 'rule', rule] + # Merge 'nat nptv6' to 'nat66 source' + merge_npt(config,base,rule) + +# Delete the original NPT configuration +config.delete(['nat','nptv6']); + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) diff --git a/src/systemd/dropbear@.service b/src/systemd/dropbear@.service index 606a7ea6d..a3fde5708 100644 --- a/src/systemd/dropbear@.service +++ b/src/systemd/dropbear@.service @@ -4,6 +4,7 @@ Requires=dropbearkey.service Wants=conserver-server.service ConditionPathExists=/run/conserver/conserver.cf After=dropbearkey.service vyos-router.service conserver-server.service +StartLimitIntervalSec=0 [Service] Type=forking @@ -11,4 +12,6 @@ ExecStartPre=/usr/bin/bash -c '/usr/bin/systemctl set-environment PORT=$(cli-she ExecStart=-/usr/sbin/dropbear -w -j -k -r /etc/dropbear/dropbear_rsa_host_key -c "/usr/bin/console %I" -P /run/conserver/dropbear.%I.pid -p ${PORT} PIDFile=/run/conserver/dropbear.%I.pid KillMode=process -Restart=on-failure +Restart=always +RestartSec=10 +RuntimeDirectoryPreserve=yes diff --git a/src/systemd/ndppd.service b/src/systemd/ndppd.service new file mode 100644 index 000000000..5790d37f1 --- /dev/null +++ b/src/systemd/ndppd.service @@ -0,0 +1,15 @@ +[Unit] +Description=NDP Proxy Daemon +After=vyos-router.service +ConditionPathExists=/run/ndppd/ndppd.conf +StartLimitIntervalSec=0 + +[Service] +Type=forking +ExecStart=/usr/sbin/ndppd -d -p /run/ndppd/ndppd.pid -c /run/ndppd/ndppd.conf +PIDFile=/run/ndppd/ndppd.pid +Restart=on-failure +RestartSec=20 + +[Install] +WantedBy=multi-user.target |