diff options
129 files changed, 2741 insertions, 3579 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..cc5e2f536 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,38 @@ +<!-- All PR should follow this template to allow a clean and transparent review --> +<!-- Text placed between these delimiters is considered a commend and is not rendered --> + +## Change Summary +<!--- Provide a general summary of your changes in the Title above --> + +## Types of changes +<!--- What types of changes does your code introduce? Put an 'x' in all the boxes that apply. --> +<!--- NOTE: Markdown requires no leading or trailing whitespace inside the [ ] for checking the box, please use [x] --> +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Code style update (formatting, renaming) +- [ ] Refactoring (no functional changes) +- [ ] Migration from an old Vyatta component to vyos-1x, please link to related PR inside obsoleted component +- [ ] Other (please describe): + +## Related Task(s) +<!-- All submitted PRs must be linked to a Task on Phabricator. --> + +## Component(s) name +<!-- A rather incomplete list of components: ethernet, wireguard, bgp, mpls, ldp, l2tp, dhcp ... --> + +## Proposed changes +<!--- Describe your changes in detail --> + +## How to test +<!--- Please describe in detail how you tested your changes. --> +<!--- Include details of your testing environment, and the tests you ran to --> + +## Checklist: +<!--- Go over all the following points, and put an `x` in all the boxes that apply. --> +<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> +<!--- The entire development process is outlined here: https://docs.vyos.io/en/latest/contributing/development.html --> +- [ ] I have read the [**CONTRIBUTING**](https://github.com/vyos/vyos-1x/blob/current/CONTRIBUTING.md) document +- [ ] I have linked this PR to one or more Phabricator Task(s) +- [ ] My commit headlines contain a valid Task id +- [ ] My change requires a change to the documentation +- [ ] I have updated the documentation accordingly diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d8177a5f5..7ac48ee4c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,90 @@ # Contributing to VyOS -You wan't to help us improve VyOS? This is awesome. We accept any kind of Pull -Requests on GitHub. To make the life of the maintainers and you as future -contributor (or maybe maintainer) much easier we have come up with some basic -rules. Instead of copy/pasting or maintaining two instances of how to contribute -to VyOS you can find the entire process documented in our online documentation: -https://docs.vyos.io/en/latest/contributing/development.html +You wan't to help us improve VyOS? This is awesome! -Also this guide might not be complete so any PR is much appreciated. +We accept any kind of Pull Requests on GitHub. In order to get your changes into +the main repository as smooth as possible please take yourself some time and +review this contribution guideline. -It might also worth browsing our blog: https://blog.vyos.io +The following paragraphs are an excerpt from our Documentation. + +## Bug Report/Issue +Issues or bugs are found in any software project. VyOS is not an exception. + +All issues should be reported to the developers. This lets the developers know +what is not working properly. Without this sort of feedback every developer +will believe that everything is working correctly. + +### I have found a bug, what should I do? + +When you believe you have found a bug, it is always a good idea to verify the +issue prior to opening a bug request. + +* Consult our [Documentation](https://docs.vyos.io) to ensure that you have + configured your system correctly +* Get community support via [Slack](https://slack.vyos.io) or our online + [Forum](https://forum.vyos.io) + +#### Ensure the problem is reproducible + +When you are able to verify that it is actually a bug, spend some time to +document how to reproduce the issue. This documentation can be invaluable. + +When you wish to have a developer fix a bug that you found, helping them +reproduce the issue is beneficial to everyone. Be sure to include information +about the hardware you are using, commands that you were running, any other +activities that you may have been doing at the time. This additional +information can be very useful. + +* What were you attempting to achieve? +* What was the configuration prior to the change? +* What commands did you use? Use e.g. ``show configuration commands`` + +#### Include output + +The output you get when you find a bug can provide lots of information. If you +get an error message on the screen, copy it exactly. Having the exact message +can provide detail that the developers can use. Like wise if you have any log +messages that also are from the time of the issue, include those. They may +also contain information that is helpful for the development team. + +### Reporting + +In order to open up a bug-report/feature request you need to create yourself +an account on [Phabricator](https://phabricator.vyos.net). On the left +side of the specific project (VyOS 1.2 or VyOS 1.3) you will find quick-links +for opening a bug-report/feature request. + +* Provide as much information as you can +* Which version of VyOS are you using? Use operational level command: + ``show version`` +* How can we reproduce this Bug? Please include a CLI configuration, you can + use ``show configuration command | strip-private`` to remove sensitive + information before publishing. + +## Feature Request + +You have an idea of how to make VyOS better or you are in need of a specific +feature which all users of VyOS would benefit from? To send a feature request +please search [Phabricator](https://phabricator.vyos.net) if there is already a +request pending. You can enhance it or if you don't find one, create a new one +by use the quick link in the left side under the specific project. + +## Code Contribution + +For contributing code to VyOS please take a short moment and review the guideline +outlined in our Documentation at +https://docs.vyos.io/en/latest/contributing/development.html#submit-a-patch + +### Coding Guidelines + +We have some small coding guidelines which are defined in a separate section of +at https://docs.vyos.io/en/latest/contributing/development.html#coding-guidelines. +The guidelines cover how to create the necessary XML structure for new features +and also how to read in the code from the CLI into the Python based scripting +backend. + +Thank you for taking the time reading this guide. + +It might also worth browsing our [Blog](https://blog.vyos.io) for additional +info and updates. @@ -76,9 +76,11 @@ interface_definitions: $(BUILD_DIR) $(obj) rm -f $(TMPL_DIR)/interfaces/wireless/node.tag/vif/node.tag/ip/node.def rm -f $(TMPL_DIR)/interfaces/wireless/node.tag/vif/node.tag/ipv6/node.def rm -f $(TMPL_DIR)/interfaces/wirelessmodem/node.tag/ipv6/node.def + rm -f $(TMPL_DIR)/interfaces/wireguard/node.tag/ipv6/node.def rm -f $(TMPL_DIR)/protocols/node.def rm -rf $(TMPL_DIR)/protocols/nbgp rm -f $(TMPL_DIR)/protocols/static/node.def + rm -f $(TMPL_DIR)/policy/node.def rm -f $(TMPL_DIR)/system/node.def rm -f $(TMPL_DIR)/vpn/node.def rm -f $(TMPL_DIR)/vpn/ipsec/node.def @@ -100,7 +102,6 @@ op_mode_definitions: rm -f $(OP_TMPL_DIR)/show/ipv6/node.def rm -f $(OP_TMPL_DIR)/show/ipv6/bgp/node.def rm -f $(OP_TMPL_DIR)/show/ipv6/route/node.def - rm -f $(OP_TMPL_DIR)/restart/node.def rm -f $(OP_TMPL_DIR)/monitor/node.def rm -f $(OP_TMPL_DIR)/generate/node.def rm -f $(OP_TMPL_DIR)/show/system/node.def diff --git a/data/configd-include.json b/data/configd-include.json index 2e44405ee..eb1dd13f9 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -29,6 +29,7 @@ "lldp.py", "nat.py", "ntp.py", +"policy-local-route.py", "protocols_igmp.py", "protocols_isis.py", "protocols_mpls.py", @@ -46,7 +47,7 @@ "system-ip.py", "system-ipv6.py", "system-login-banner.py", -"system-options.py", +"system-option.py", "system-syslog.py", "system-timezone.py", "system_console.py", diff --git a/data/templates/accel-ppp/chap-secrets.ipoe.tmpl b/data/templates/accel-ppp/chap-secrets.ipoe.tmpl index a7d899354..1df878fcf 100644 --- a/data/templates/accel-ppp/chap-secrets.ipoe.tmpl +++ b/data/templates/accel-ppp/chap-secrets.ipoe.tmpl @@ -1,18 +1,18 @@ # username server password acceptable local IP addresses shaper -{% for interface in auth_interfaces -%} -{% for mac in interface.mac -%} -{% if mac.rate_upload and mac.rate_download -%} -{% if mac.vlan_id -%} +{% for interface in auth_interfaces %} +{% for mac in interface.mac %} +{% if mac.rate_upload and mac.rate_download %} +{% if mac.vlan_id %} {{ interface.name }}.{{ mac.vlan_id }} * {{ mac.address | lower }} * {{ mac.rate_download }}/{{ mac.rate_upload }} -{% else -%} +{% else %} {{ interface.name }} * {{ mac.address | lower }} * {{ mac.rate_download }}/{{ mac.rate_upload }} -{% endif -%} -{% else -%} -{% if mac.vlan_id -%} +{% endif %} +{% else %} +{% if mac.vlan_id %} {{ interface.name }}.{{ mac.vlan_id }} * {{ mac.address | lower }} * -{% else -%} +{% else %} {{ interface.name }} * {{ mac.address | lower }} * -{% endif -%} -{% endif -%} -{% endfor -%} -{% endfor -%} +{% endif %} +{% endif %} +{% endfor %} +{% endfor %} diff --git a/data/templates/accel-ppp/config_chap_secrets_radius.j2 b/data/templates/accel-ppp/config_chap_secrets_radius.j2 index c94e75a23..52d5d40e7 100644 --- a/data/templates/accel-ppp/config_chap_secrets_radius.j2 +++ b/data/templates/accel-ppp/config_chap_secrets_radius.j2 @@ -27,7 +27,7 @@ called-sid={{ authentication.radius.called_sid_format }} {% endif %}
{% if authentication.radius.dynamic_author.server is defined and authentication.radius.dynamic_author.server is not none %}
dae-server={{ authentication.radius.dynamic_author.server }}:{{ authentication.radius.dynamic_author.port }},{{ authentication.radius.dynamic_author.key }}
-{% endif -%}
+{% endif %}
{% endif %}
{# Both chap-secrets and radius block required the gw-ip-address #}
{% if gateway_address is defined and gateway_address is not none %}
diff --git a/data/templates/accel-ppp/ipoe.config.tmpl b/data/templates/accel-ppp/ipoe.config.tmpl index ab61f7f5a..1cf2ab0be 100644 --- a/data/templates/accel-ppp/ipoe.config.tmpl +++ b/data/templates/accel-ppp/ipoe.config.tmpl @@ -35,7 +35,7 @@ password=csid {% endif %} proxy-arp=1 -{%- for interface in interfaces %} +{% for interface in interfaces %} {% if (interface.shared == '0') and (interface.vlan_mon) %} vlan-mon={{ interface.name }},{{ interface.vlan_mon | join(',') }} {% endif %} @@ -43,16 +43,16 @@ vlan-mon={{ interface.name }},{{ interface.vlan_mon | join(',') }} {% if dnsv4 %} [dns] -{% for dns in dnsv4 -%} +{% for dns in dnsv4 %} dns{{ loop.index }}={{ dns }} -{% endfor -%} +{% endfor %} {% endif %} {% if dnsv6 %} [ipv6-dns] -{% for dns in dnsv6 -%} +{% for dns in dnsv6 %} {{ dns }} -{% endfor -%} +{% endfor %} {% endif %} [ipv6-nd] @@ -79,7 +79,7 @@ chap-secrets={{ chap_secrets_file }} verbose=1 {% for r in radius_server %} server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} -{% endfor -%} +{% endfor %} {% if radius_acct_inter_jitter %} acct-interim-jitter={{ radius_acct_inter_jitter }} @@ -90,17 +90,17 @@ timeout={{ radius_timeout }} max-try={{ radius_max_try }} {% if radius_nas_id %} nas-identifier={{ radius_nas_id }} -{% endif -%} +{% endif %} {% if radius_nas_ip %} nas-ip-address={{ radius_nas_ip }} -{% endif -%} +{% endif %} {% if radius_source_address %} bind={{ radius_source_address }} -{% endif -%} +{% endif %} {% if radius_dynamic_author %} dae-server={{ radius_dynamic_author.server }}:{{ radius_dynamic_author.port }},{{ radius_dynamic_author.key }} -{% endif -%} +{% endif %} {% if radius_shaper_attr %} [shaper] @@ -108,8 +108,8 @@ verbose=1 attr={{ radius_shaper_attr }} {% if radius_shaper_vendor %} vendor={{ radius_shaper_vendor }} -{% endif -%} -{% endif -%} +{% endif %} +{% endif %} {% endif %} [cli] diff --git a/data/templates/accel-ppp/l2tp.config.tmpl b/data/templates/accel-ppp/l2tp.config.tmpl index 9a3c27912..070a966b7 100644 --- a/data/templates/accel-ppp/l2tp.config.tmpl +++ b/data/templates/accel-ppp/l2tp.config.tmpl @@ -9,7 +9,7 @@ chap-secrets {% if auth_mode == 'radius' %} radius -{% endif -%} +{% endif %} ippool shaper @@ -27,23 +27,23 @@ level=5 {% if dnsv4 %} [dns] -{% for dns in dnsv4 -%} +{% for dns in dnsv4 %} dns{{ loop.index }}={{ dns }} -{% endfor -%} +{% endfor %} {% endif %} {% if dnsv6 %} [ipv6-dns] -{% for dns in dnsv6 -%} +{% for dns in dnsv6 %} {{ dns }} -{% endfor -%} +{% endfor %} {% endif %} {% if wins %} [wins] -{% for server in wins -%} +{% for server in wins %} wins{{ loop.index }}={{ server }} -{% endfor -%} +{% endfor %} {% endif %} [l2tp] @@ -65,11 +65,11 @@ secret={{ lns_shared_secret }} [ip-pool] {% if client_ip_pool %} {{ client_ip_pool }} -{% endif -%} +{% endif %} {% if client_ip_subnets %} {% for sn in client_ip_subnets %} {{sn}} -{% endfor -%} +{% endfor %} {% endif %} {% endif %} {% if gateway_address %} @@ -84,7 +84,7 @@ chap-secrets={{ chap_secrets_file }} verbose=1 {% for r in radius_server %} server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} -{% endfor -%} +{% endfor %} {% if radius_acct_inter_jitter %} acct-interim-jitter={{ radius_acct_inter_jitter }} @@ -96,13 +96,13 @@ max-try={{ radius_max_try }} {% if radius_nas_id %} nas-identifier={{ radius_nas_id }} -{% endif -%} +{% endif %} {% if radius_nas_ip %} nas-ip-address={{ radius_nas_ip }} -{% endif -%} +{% endif %} {% if radius_source_address %} bind={{ radius_source_address }} -{% endif -%} +{% endif %} {% endif %} {% if gateway_address %} gw-ip-address={{ gateway_address }} @@ -144,7 +144,7 @@ verbose=1 attr={{ radius_shaper_attr }} {% if radius_shaper_vendor %} vendor={{ radius_shaper_vendor }} -{% endif -%} +{% endif %} {% endif %} [cli] diff --git a/data/templates/accel-ppp/pppoe.config.tmpl b/data/templates/accel-ppp/pppoe.config.tmpl index 19adbc890..1d6c1fefc 100644 --- a/data/templates/accel-ppp/pppoe.config.tmpl +++ b/data/templates/accel-ppp/pppoe.config.tmpl @@ -108,7 +108,7 @@ interface=re:{{ interface.name }}\.\d+ {% if service_name %} service-name={{ service_name | join(',') }} -{% endif -%} +{% endif %} {% if pado_delay %} pado-delay={{ pado_delay }} diff --git a/data/templates/accel-ppp/pptp.config.tmpl b/data/templates/accel-ppp/pptp.config.tmpl index dc77b42e9..5a6cfe749 100644 --- a/data/templates/accel-ppp/pptp.config.tmpl +++ b/data/templates/accel-ppp/pptp.config.tmpl @@ -7,7 +7,7 @@ ippool chap-secrets {% elif auth_mode == 'radius' %} radius -{% endif -%} +{% endif %} {% for proto in auth_proto %} {{proto}} {% endfor %} @@ -22,16 +22,16 @@ level=5 {% if dnsv4 %} [dns] -{% for dns in dnsv4 -%} +{% for dns in dnsv4 %} dns{{ loop.index }}={{ dns }} -{% endfor -%} +{% endfor %} {% endif %} {% if wins %} [wins] -{% for server in wins -%} +{% for server in wins %} wins{{ loop.index }}={{ server }} -{% endfor -%} +{% endfor %} {% endif %} @@ -67,7 +67,7 @@ chap-secrets={{ chap_secrets_file }} verbose=1 {% for r in radius_server %} server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} -{% endfor -%} +{% endfor %} {% if radius_acct_inter_jitter %} acct-interim-jitter={{ radius_acct_inter_jitter }} @@ -79,13 +79,13 @@ max-try={{ radius_max_try }} {% if radius_nas_id %} nas-identifier={{ radius_nas_id }} -{% endif -%} +{% endif %} {% if radius_nas_ip %} nas-ip-address={{ radius_nas_ip }} -{% endif -%} +{% endif %} {% if radius_source_address %} bind={{ radius_source_address }} -{% endif -%} +{% endif %} {% endif %} [cli] diff --git a/data/templates/bcast-relay/udp-broadcast-relay.tmpl b/data/templates/bcast-relay/udp-broadcast-relay.tmpl index d0c7d8bf9..73e9acad4 100644 --- a/data/templates/bcast-relay/udp-broadcast-relay.tmpl +++ b/data/templates/bcast-relay/udp-broadcast-relay.tmpl @@ -1,7 +1,7 @@ ### Autogenerated by bcast_relay.py ### # UDP broadcast relay configuration for instance {{ id }} -{%- if description %} +{% if description %} # Comment: {{ description }} {% endif %} DAEMON_ARGS="{{ '-s ' + address if address is defined }} {{ instance }} {{ port }} {{ interface | join(' ') }}" diff --git a/data/templates/dhcp-relay/config.tmpl b/data/templates/dhcp-relay/config.tmpl deleted file mode 100644 index b223807cf..000000000 --- a/data/templates/dhcp-relay/config.tmpl +++ /dev/null @@ -1,4 +0,0 @@ -### Autogenerated by dhcp_relay.py ### - -# Defaults for isc-dhcp-relay6.service -OPTIONS="{{ options | join(' ') }} -i {{ interface | join(' -i ') }} {{ server | join(' ') }}" diff --git a/data/templates/dhcp-relay/dhcrelay.conf.tmpl b/data/templates/dhcp-relay/dhcrelay.conf.tmpl new file mode 100644 index 000000000..a9d17ed9a --- /dev/null +++ b/data/templates/dhcp-relay/dhcrelay.conf.tmpl @@ -0,0 +1,6 @@ +### Autogenerated by dhcp_relay.py ### + +{% set max_size = '-A ' + relay_options.max_size if relay_options.max_size is defined and relay_options.max_size is not none %} +{# hop_count and relay_agents_packets is a default option, thus it is always present #} +OPTIONS="-c {{ relay_options.hop_count }} -a -m {{ relay_options.relay_agents_packets }} {{ max_size }} -i {{ interface | join(' -i ') }} {{ server | join(' ') }}" + diff --git a/data/templates/dhcp-relay/dhcrelay6.conf.tmpl b/data/templates/dhcp-relay/dhcrelay6.conf.tmpl new file mode 100644 index 000000000..58c216b7c --- /dev/null +++ b/data/templates/dhcp-relay/dhcrelay6.conf.tmpl @@ -0,0 +1,21 @@ +### Autogenerated by dhcpv6_relay.py ### + +{# upstream_interface is mandatory so it's always present #} +{% set upstream = namespace(value='') %} +{% for interface, config in upstream_interface.items() %} +{% for address in config.address %} +{% set upstream.value = upstream.value + '-u ' + address + '%' + interface + ' ' %} +{% endfor %} +{% endfor %} +{# listen_interface is mandatory so it's always present #} +{% set listen = namespace(value='') %} +{% for interface, config in listen_interface.items() %} +{% if config.address is defined and config.address is not none %} +{% set listen.value = listen.value + '-l ' + config.address + '%' + interface + ' ' %} +{% else %} +{% set listen.value = listen.value + '-l ' + interface + ' ' %} +{% endif %} +{% endfor %} + +OPTIONS="{{ listen.value }} {{ upstream.value }} -c {{ max_hop_count }} {{ '-I' if use_interface_id_option is defined }}" + diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl index 5f5129451..f431d3207 100644 --- a/data/templates/dhcp-server/dhcpd.conf.tmpl +++ b/data/templates/dhcp-server/dhcpd.conf.tmpl @@ -23,38 +23,38 @@ on expiry { execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "release", ClientName, ClientIp, ClientMac, ClientDomain); } {% endif %} -{%- if host_decl_name %} +{% if host_decl_name %} use-host-decl-names on; -{%- endif %} -ddns-update-style {% if ddns_enable -%} interim {%- else -%} none {%- endif %}; -{% if static_route -%} +{% endif %} +ddns-update-style {% if ddns_enable %} interim {% else %} none {% endif %}; +{% if static_route %} option rfc3442-static-route code 121 = array of integer 8; option windows-static-route code 249 = array of integer 8; -{%- endif %} -{% if wpad -%} +{% endif %} +{% if wpad %} option wpad-url code 252 = text; {% endif %} -{%- if global_parameters %} +{% if global_parameters %} # The following {{ global_parameters | length }} line(s) were added as global-parameters in the CLI and have not been validated -{%- for param in global_parameters %} +{% for param in global_parameters %} {{ param }} -{%- endfor -%} -{%- endif %} +{% endfor %} +{% endif %} # Failover configuration {% for network in shared_network %} -{%- if not network.disabled -%} -{%- for subnet in network.subnet %} -{%- if subnet.failover_name -%} +{% if not network.disabled %} +{% for subnet in network.subnet %} +{% if subnet.failover_name %} failover peer "{{ subnet.failover_name }}" { -{%- if subnet.failover_status == 'primary' %} +{% if subnet.failover_status == 'primary' %} primary; mclt 1800; split 128; -{%- elif subnet.failover_status == 'secondary' %} +{% elif subnet.failover_status == 'secondary' %} secondary; -{%- endif %} +{% endif %} address {{ subnet.failover_local_addr }}; port 520; peer address {{ subnet.failover_peer_addr }}; @@ -63,133 +63,133 @@ failover peer "{{ subnet.failover_name }}" { max-unacked-updates 10; load balance max seconds 3; } -{% endif -%} -{% endfor -%} -{% endif -%} +{% endif %} +{% endfor %} +{% endif %} {% endfor %} # Shared network configration(s) {% for network in shared_network %} -{%- if not network.disabled -%} +{% if not network.disabled %} shared-network {{ network.name }} { - {%- if network.authoritative %} + {% if network.authoritative %} authoritative; - {%- endif %} - {%- if network.network_parameters %} + {% endif %} + {% if network.network_parameters %} # The following {{ network.network_parameters | length }} line(s) were added as shared-network-parameters in the CLI and have not been validated - {%- for param in network.network_parameters %} + {% for param in network.network_parameters %} {{ param }} - {%- endfor %} - {%- endif %} - {%- for subnet in network.subnet %} + {% endfor %} + {% endif %} + {% for subnet in network.subnet %} subnet {{ subnet.address }} netmask {{ subnet.netmask }} { - {%- if subnet.dns_server %} + {% if subnet.dns_server %} option domain-name-servers {{ subnet.dns_server | join(', ') }}; - {%- endif %} - {%- if subnet.domain_search %} + {% endif %} + {% if subnet.domain_search %} option domain-search {{ subnet.domain_search | join(', ') }}; - {%- endif %} - {%- if subnet.ntp_server %} + {% endif %} + {% if subnet.ntp_server %} option ntp-servers {{ subnet.ntp_server | join(', ') }}; - {%- endif %} - {%- if subnet.pop_server %} + {% endif %} + {% if subnet.pop_server %} option pop-server {{ subnet.pop_server | join(', ') }}; - {%- endif %} - {%- if subnet.smtp_server %} + {% endif %} + {% if subnet.smtp_server %} option smtp-server {{ subnet.smtp_server | join(', ') }}; - {%- endif %} - {%- if subnet.time_server %} + {% endif %} + {% if subnet.time_server %} option time-servers {{ subnet.time_server | join(', ') }}; - {%- endif %} - {%- if subnet.wins_server %} + {% endif %} + {% if subnet.wins_server %} option netbios-name-servers {{ subnet.wins_server | join(', ') }}; - {%- endif %} - {%- if subnet.static_route %} + {% endif %} + {% if subnet.static_route %} option rfc3442-static-route {{ subnet.static_route }}{% if subnet.rfc3442_default_router %}, {{ subnet.rfc3442_default_router }}{% endif %}; option windows-static-route {{ subnet.static_route }}; - {%- endif %} - {%- if subnet.ip_forwarding %} + {% endif %} + {% if subnet.ip_forwarding %} option ip-forwarding true; - {%- endif -%} - {%- if subnet.default_router %} + {% endif %} + {% if subnet.default_router %} option routers {{ subnet.default_router }}; - {%- endif -%} - {%- if subnet.server_identifier %} + {% endif %} + {% if subnet.server_identifier %} option dhcp-server-identifier {{ subnet.server_identifier }}; - {%- endif -%} - {%- if subnet.domain_name %} + {% endif %} + {% if subnet.domain_name %} option domain-name "{{ subnet.domain_name }}"; - {%- endif -%} - {%- if subnet.subnet_parameters %} + {% endif %} + {% if subnet.subnet_parameters %} # The following {{ subnet.subnet_parameters | length }} line(s) were added as subnet-parameters in the CLI and have not been validated - {%- for param in subnet.subnet_parameters %} + {% for param in subnet.subnet_parameters %} {{ param }} - {%- endfor -%} - {%- endif %} - {%- if subnet.tftp_server %} + {% endfor %} + {% endif %} + {% if subnet.tftp_server %} option tftp-server-name "{{ subnet.tftp_server }}"; - {%- endif -%} - {%- if subnet.bootfile_name %} + {% endif %} + {% if subnet.bootfile_name %} option bootfile-name "{{ subnet.bootfile_name }}"; filename "{{ subnet.bootfile_name }}"; - {%- endif -%} - {%- if subnet.bootfile_server %} + {% endif %} + {% if subnet.bootfile_server %} next-server {{ subnet.bootfile_server }}; - {%- endif -%} - {%- if subnet.time_offset %} + {% endif %} + {% if subnet.time_offset %} option time-offset {{ subnet.time_offset }}; - {%- endif -%} - {%- if subnet.wpad_url %} + {% endif %} + {% if subnet.wpad_url %} option wpad-url "{{ subnet.wpad_url }}"; - {%- endif -%} - {%- if subnet.client_prefix_length %} + {% endif %} + {% if subnet.client_prefix_length %} option subnet-mask {{ subnet.client_prefix_length }}; - {%- endif -%} + {% endif %} {% if subnet.lease %} default-lease-time {{ subnet.lease }}; max-lease-time {{ subnet.lease }}; - {%- endif -%} - {%- for host in subnet.static_mapping %} - {% if not host.disabled -%} - host {% if host_decl_name -%} {{ host.name }} {%- else -%} {{ network.name }}_{{ host.name }} {%- endif %} { - {%- if host.ip_address %} + {% endif %} + {% for host in subnet.static_mapping %} + {% if not host.disabled %} + host {% if host_decl_name %} {{ host.name }} {% else %} {{ network.name }}_{{ host.name }} {% endif %} { + {% if host.ip_address %} fixed-address {{ host.ip_address }}; - {%- endif %} + {% endif %} hardware ethernet {{ host.mac_address }}; - {%- if host.static_parameters %} + {% if host.static_parameters %} # The following {{ host.static_parameters | length }} line(s) were added as static-mapping-parameters in the CLI and have not been validated - {%- for param in host.static_parameters %} + {% for param in host.static_parameters %} {{ param }} - {%- endfor -%} - {%- endif %} + {% endfor %} + {% endif %} } - {%- endif %} - {%- endfor %} - {%- if subnet.failover_name %} + {% endif %} + {% endfor %} + {% if subnet.failover_name %} pool { failover peer "{{ subnet.failover_name }}"; deny dynamic bootp clients; - {%- for range in subnet.range %} + {% for range in subnet.range %} range {{ range.start }} {{ range.stop }}; - {%- endfor %} + {% endfor %} } - {%- else %} - {%- for range in subnet.range %} + {% else %} + {% for range in subnet.range %} range {{ range.start }} {{ range.stop }}; - {%- endfor %} - {%- endif %} + {% endfor %} + {% endif %} } - {%- endfor %} + {% endfor %} on commit { set shared-networkname = "{{ network.name }}"; - {% if hostfile_update -%} + {% if hostfile_update %} set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name); set ClientIp = binary-to-ascii(10, 8, ".", leased-address); set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6)); set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!"); execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "commit", ClientName, ClientIp, ClientMac, ClientDomain); - {%- endif %} + {% endif %} } } -{%- endif %} +{% endif %} {% endfor %} diff --git a/data/templates/dhcpv6-relay/config.tmpl b/data/templates/dhcpv6-relay/config.tmpl deleted file mode 100644 index 55035ae6c..000000000 --- a/data/templates/dhcpv6-relay/config.tmpl +++ /dev/null @@ -1,4 +0,0 @@ -### Autogenerated by dhcpv6_relay.py ### - -# Defaults for isc-dhcp-relay6.service -OPTIONS="-l {{ listen_addr | join(' -l ') }} -u {{ upstream_addr | join(' -u ') }} {{ options | join(' ') }}" diff --git a/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl b/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl index bdeea71da..aa6d7fb5d 100644 --- a/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl +++ b/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl @@ -4,87 +4,87 @@ # https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html log-facility local7; -{%- if preference %} +{% if preference %} option dhcp6.preference {{ preference }}; -{%- endif %} +{% endif %} # Shared network configration(s) {% for network in shared_network %} -{%- if not network.disabled -%} +{% if not network.disabled %} shared-network {{ network.name }} { - {%- if network.common.info_refresh_time %} + {% if network.common.info_refresh_time %} option dhcp6.info-refresh-time {{ network.common.info_refresh_time }}; - {%- endif %} - {%- if network.common.domain_search %} + {% endif %} + {% if network.common.domain_search %} option dhcp6.domain-search "{{ network.common.domain_search | join('", "') }}"; - {%- endif %} - {%- if network.common.dns_server %} + {% endif %} + {% if network.common.dns_server %} option dhcp6.name-servers {{ network.common.dns_server | join(', ') }}; - {%- endif %} - {%- for subnet in network.subnet %} + {% endif %} + {% for subnet in network.subnet %} subnet6 {{ subnet.network }} { - {%- for range in subnet.range6_prefix %} + {% for range in subnet.range6_prefix %} range6 {{ range.prefix }}{{ " temporary" if range.temporary }}; - {%- endfor %} - {%- for range in subnet.range6 %} + {% endfor %} + {% for range in subnet.range6 %} range6 {{ range.start }} {{ range.stop }}; - {%- endfor %} - {%- if subnet.domain_search %} + {% endfor %} + {% if subnet.domain_search %} option dhcp6.domain-search "{{ subnet.domain_search | join('", "') }}"; - {%- endif %} - {%- if subnet.lease_def %} + {% endif %} + {% if subnet.lease_def %} default-lease-time {{ subnet.lease_def }}; - {%- endif %} - {%- if subnet.lease_max %} + {% endif %} + {% if subnet.lease_max %} max-lease-time {{ subnet.lease_max }}; - {%- endif %} - {%- if subnet.lease_min %} + {% endif %} + {% if subnet.lease_min %} min-lease-time {{ subnet.lease_min }}; - {%- endif %} - {%- if subnet.dns_server %} + {% endif %} + {% if subnet.dns_server %} option dhcp6.name-servers {{ subnet.dns_server | join(', ') }}; - {%- endif %} - {%- if subnet.nis_domain %} + {% endif %} + {% if subnet.nis_domain %} option dhcp6.nis-domain-name "{{ subnet.nis_domain }}"; - {%- endif %} - {%- if subnet.nis_server %} + {% endif %} + {% if subnet.nis_server %} option dhcp6.nis-servers {{ subnet.nis_server | join(', ') }}; - {%- endif %} - {%- if subnet.nisp_domain %} + {% endif %} + {% if subnet.nisp_domain %} option dhcp6.nisp-domain-name "{{ subnet.nisp_domain }}"; - {%- endif %} - {%- if subnet.nisp_server %} + {% endif %} + {% if subnet.nisp_server %} option dhcp6.nisp-servers {{ subnet.nisp_server | join(', ') }}; - {%- endif %} - {%- if subnet.sip_address %} + {% endif %} + {% if subnet.sip_address %} option dhcp6.sip-servers-addresses {{ subnet.sip_address | join(', ') }}; - {%- endif %} - {%- if subnet.sip_hostname %} + {% endif %} + {% if subnet.sip_hostname %} option dhcp6.sip-servers-names "{{ subnet.sip_hostname | join('", "') }}"; - {%- endif %} - {%- if subnet.sntp_server %} + {% endif %} + {% if subnet.sntp_server %} option dhcp6.sntp-servers {{ subnet.sntp_server | join(', ') }}; - {%- endif %} - {%- for prefix in subnet.prefix_delegation %} + {% endif %} + {% for prefix in subnet.prefix_delegation %} prefix6 {{ prefix.start }} {{ prefix.stop }} /{{ prefix.length }}; - {%- endfor %} - {%- for host in subnet.static_mapping %} - {% if not host.disabled -%} + {% endfor %} + {% for host in subnet.static_mapping %} + {% if not host.disabled %} host {{ network.name }}_{{ host.name }} { - {%- if host.client_identifier %} + {% if host.client_identifier %} host-identifier option dhcp6.client-id {{ host.client_identifier }}; - {%- endif %} - {%- if host.ipv6_address %} + {% endif %} + {% if host.ipv6_address %} fixed-address6 {{ host.ipv6_address }}; - {%- endif %} + {% endif %} } - {%- endif %} - {%- endfor %} + {% endif %} + {% endfor %} } - {%- endfor %} + {% endfor %} on commit { set shared-networkname = "{{ network.name }}"; } } -{%- endif %} +{% endif %} {% endfor %} diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 286c21859..8769c2384 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -1,161 +1,143 @@ #!/usr/sbin/nft -f -# Start with clean NAT table -flush table nat - -{% if helper_functions == 'remove' %} -{# NAT if going to be disabled - remove rules and targets from nftables #} - -{% set base_command = "delete rule ip raw" %} -{{ base_command }} PREROUTING handle {{ pre_ct_ignore }} -{{ base_command }} OUTPUT handle {{ out_ct_ignore }} -{{ base_command }} PREROUTING handle {{ pre_ct_conntrack }} -{{ base_command }} OUTPUT handle {{ out_ct_conntrack }} - -delete chain ip raw NAT_CONNTRACK - -{% elif helper_functions == 'add' %} -{# NAT if enabled - add targets to nftables #} -add chain ip raw NAT_CONNTRACK -add rule ip raw NAT_CONNTRACK counter accept - -{% set base_command = "add rule ip raw" %} - -{{ base_command }} PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER -{{ base_command }} OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER -{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK -{{ base_command }} OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK -{% endif %} - -{% macro nat_rule(rule, chain) %} +{% macro nat_rule(rule, config, chain) %} {% set comment = "" %} {% set base_log = "" %} - -{% set src_addr = "ip saddr " + rule.source_address if rule.source_address %} -{% set dst_addr = "ip daddr " + rule.dest_address if rule.dest_address %} - +{% set src_addr = "ip saddr " + config.source.address if config.source is defined and config.source.address is defined and config.source.address is not none %} +{% set dst_addr = "ip daddr " + config.destination.address if config.destination is defined and config.destination.address is defined and config.destination.address is not none %} {# negated port groups need special treatment, move != in front of { } group #} -{% if rule.source_port.startswith('!=') %} -{% set src_port = "sport != { " + rule.source_port.replace('!=','') +" }" if rule.source_port %} +{% if config.source is defined and config.source.port is defined and config.source.port is not none and config.source.port.startswith('!=') %} +{% set src_port = "sport != { " + config.source.port.replace('!=','') +" }" %} {% else %} -{% set src_port = "sport { " + rule.source_port +" }" if rule.source_port %} +{% set src_port = "sport { " + config.source.port +" }" if config.source is defined and config.source.port is defined and config.source.port is not none %} {% endif %} - {# negated port groups need special treatment, move != in front of { } group #} -{% if rule.dest_port.startswith('!=') %} -{% set dst_port = "dport != { " + rule.dest_port.replace('!=','') +" }" if rule.dest_port %} +{% if config.destination is defined and config.destination.port is defined and config.destination.port is not none and config.destination.port.startswith('!=') %} +{% set dst_port = "dport != { " + config.destination.port.replace('!=','') +" }" %} {% else %} -{% set dst_port = "dport { " + rule.dest_port +" }" if rule.dest_port %} +{% set dst_port = "dport { " + config.destination.port +" }" if config.destination is defined and config.destination.port is defined and config.destination.port is not none %} {% endif %} - {% if chain == "PREROUTING" %} -{% set comment = "DST-NAT-" + rule.number %} -{% set base_log = "[NAT-DST-" + rule.number %} -{% set interface = " iifname \"" + rule.interface_in + "\"" if rule.interface_in is defined and rule.interface_in != 'any' else '' %} -{% set trns_addr = "dnat to " + rule.translation_address %} - +{% set comment = "DST-NAT-" + rule %} +{% set base_log = "[NAT-DST-" + rule %} +{% set interface = " iifname \"" + config.inbound_interface + "\"" if config.inbound_interface is defined and config.inbound_interface != 'any' else '' %} +{% set trns_addr = "dnat to " + 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 comment = "SRC-NAT-" + rule.number %} -{% set base_log = "[NAT-SRC-" + rule.number %} -{% set interface = " oifname \"" + rule.interface_out + "\"" if rule.interface_out is defined and rule.interface_out != 'any' else '' %} -{% if rule.translation_address == 'masquerade' %} -{% set trns_addr = rule.translation_address %} -{% if rule.translation_port %} +{% set comment = "SRC-NAT-" + rule %} +{% set base_log = "[NAT-SRC-" + rule %} +{% set interface = " oifname \"" + config.outbound_interface + "\"" if config.outbound_interface is defined and config.outbound_interface != 'any' else '' %} +{% if config.translation is defined and config.translation.address is defined and config.translation.address == 'masquerade' %} +{% set trns_addr = config.translation.address %} +{% if config.translation.port is defined and config.translation.port is not none %} {% set trns_addr = trns_addr + " to " %} {% endif %} {% else %} -{% set trns_addr = "snat to " + rule.translation_address %} +{% set trns_addr = "snat to " + config.translation.address if config.translation is defined and config.translation.address is defined and config.translation.address is not none %} {% endif %} {% endif %} -{% set trns_port = ":" + rule.translation_port if rule.translation_port %} - -{% if rule.protocol == "tcp_udp" %} +{% set trns_port = ":" + config.translation.port if config.translation is defined and config.translation.port is defined and config.translation.port is not none %} +{# protocol has a default value thus it is always present #} +{% if config.protocol == "tcp_udp" %} {% set protocol = "tcp" %} {% set comment = comment + " tcp_udp" %} {% else %} -{% set protocol = rule.protocol %} +{% set protocol = config.protocol %} {% endif %} - -{% if rule.log %} -{% if rule.exclude %} +{% if config.log is defined %} +{% if config.exclude is defined %} {% set log = base_log + "-EXCL]" %} -{% elif rule.translation_address == 'masquerade' %} +{% elif config.translation is defined and config.translation.address is defined and config.translation.address == 'masquerade' %} {% set log = base_log + "-MASQ]" %} {% else %} {% set log = base_log + "]" %} {% endif %} {% endif %} - -{% if rule.exclude %} +{% if config.exclude is defined %} {# rule has been marked as "exclude" thus we simply return here #} {% set trns_addr = "return" %} {% set trns_port = "" %} {% endif %} - {% set output = "add rule ip nat " + chain + interface %} - {% if protocol != "all" %} {% set output = output + " ip protocol " + protocol %} {% endif %} - {% if src_addr %} {% set output = output + " " + src_addr %} {% endif %} {% if src_port %} {% set output = output + " " + protocol + " " + src_port %} {% endif %} - {% if dst_addr %} {% set output = output + " " + dst_addr %} {% endif %} {% if dst_port %} {% set output = output + " " + protocol + " " + dst_port %} {% endif %} - {# 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 trns_addr %} {% set output = output + " " + trns_addr %} {% endif %} - {% if trns_port %} {# Do not add a whitespace here, translation port must be directly added after IP address #} {# e.g. 192.0.2.10:3389 #} {% set output = output + trns_port %} {% endif %} - {% if comment %} {% set output = output + " comment \"" + comment + "\"" %} {% endif %} - {{ log_output if log_output }} {{ output }} - {# Special handling if protocol is tcp_udp, we must repeat the entire rule with udp as protocol #} -{% if rule.protocol == "tcp_udp" %} +{% if config.protocol == "tcp_udp" %} {# Beware of trailing whitespace, without it the comment tcp_udp will be changed to udp_udp #} {{ log_output | replace("tcp ", "udp ") if log_output }} {{ output | replace("tcp ", "udp ") }} {% endif %} {% endmacro %} +# Start with clean NAT table +flush table nat +{% if helper_functions == 'remove' %} +{# NAT if going to be disabled - remove rules and targets from nftables #} +{% set base_command = "delete rule ip raw" %} +{{ base_command }} PREROUTING handle {{ pre_ct_ignore }} +{{ base_command }} OUTPUT handle {{ out_ct_ignore }} +{{ base_command }} PREROUTING handle {{ pre_ct_conntrack }} +{{ base_command }} OUTPUT handle {{ out_ct_conntrack }} + +delete chain ip raw NAT_CONNTRACK + +{% elif helper_functions == 'add' %} +{# NAT if enabled - add targets to nftables #} +add chain ip raw NAT_CONNTRACK +add rule ip raw NAT_CONNTRACK counter accept +{% set base_command = "add rule ip raw" %} +{{ base_command }} PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER +{{ base_command }} OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER +{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK +{{ base_command }} OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK +{% endif %} + # # Destination NAT rules build up here # -{% for rule in destination if not rule.disabled -%} -{{ nat_rule(rule, 'PREROUTING') }} -{% endfor %} +{% 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 %} +{{ nat_rule(rule, config, 'PREROUTING') }} +{% endfor %} +{% endif %} # # Source NAT rules build up here # -{% for rule in source if not rule.disabled -%} -{{ nat_rule(rule, 'POSTROUTING') }} -{% endfor %} +{% 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 %} +{{ nat_rule(rule, config, 'POSTROUTING') }} +{% endfor %} +{% endif %} diff --git a/data/templates/frr/bfd.frr.tmpl b/data/templates/frr/bfd.frr.tmpl index 7df4bfd01..95a29e06a 100644 --- a/data/templates/frr/bfd.frr.tmpl +++ b/data/templates/frr/bfd.frr.tmpl @@ -1,10 +1,10 @@ ! bfd -{% for peer in old_peers -%} +{% for peer in old_peers %} no peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.src_addr %} local-address {{ peer.src_addr }}{% endif %}{% if peer.src_if %} interface {{ peer.src_if }}{% endif %} -{% endfor -%} +{% endfor %} ! -{% for peer in new_peers -%} +{% for peer in new_peers %} peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.src_addr %} local-address {{ peer.src_addr }}{% endif %}{% if peer.src_if %} interface {{ peer.src_if }}{% endif %} detect-multiplier {{ peer.multiplier }} receive-interval {{ peer.rx_interval }} @@ -12,5 +12,5 @@ bfd {% if peer.echo_mode %}echo-mode{% endif %} {% if peer.echo_interval != '' %}echo-interval {{ peer.echo_interval }}{% endif %} {% if not peer.shutdown %}no {% endif %}shutdown -{% endfor -%} +{% endfor %} ! diff --git a/data/templates/frr/bgp.frr.tmpl b/data/templates/frr/bgp.frr.tmpl index d0857ac2c..86e1aa366 100644 --- a/data/templates/frr/bgp.frr.tmpl +++ b/data/templates/frr/bgp.frr.tmpl @@ -1,1016 +1,287 @@ -{% set conf_bgp = nbgp -%} -{% for asn in nbgp -%} -! -router bgp {{ asn }} - no bgp default ipv4-unicast - -{#- set 'conf_bgp[asn].parameters' as bgp_params #} -{%- set bgp_params = conf_bgp[asn].parameters %} -{%- set bgp_afi = conf_bgp[asn].address_family %} - -{#- START Global ASN address-family section; set protocol bgp xxx address-family #} -{%- if 'address_family' in conf_bgp[asn] %} -{%- for type in bgp_afi %} -{%- if type == "ipv4_unicast" %} +{### MACRO definition for recurring peer patter, this can be either fed by a ###} +{### peer-group or an individual BGP neighbor ###} +{% macro bgp_neighbor(neighbor, config, peer_group=false) %} +{% if peer_group == true %} + neighbor {{ neighbor }} peer-group +{% elif config.peer_group is defined and config.peer_group is not none %} + neighbor {{ neighbor }} peer-group {{ config.peer_group }} +{% endif %} +{% if config.remote_as is defined and config.remote_as is not none %} + neighbor {{ neighbor }} remote-as {{ config.remote_as }} +{% endif %} +{% if config.bfd is defined %} + neighbor {{ neighbor }} bfd +{% endif %} +{% if config.capability is defined and config.capability is not none %} +{% if config.capability.dynamic is defined %} + neighbor {{ neighbor }} capability dynamic +{% endif %} +{% if config.capability.extended_nexthop is defined %} + neighbor {{ neighbor }} capability extended-nexthop +{% endif %} +{% endif %} +{% if config.description is defined and config.description is not none %} + neighbor {{ neighbor }} description {{ config.description }} +{% endif %} +{% if config.disable_capability_negotiation is defined %} + neighbor {{ neighbor }} disable-capability-negotiation +{% endif %} +{% if config.ebgp_multihop is defined and config.ebgp_multihop is not none %} + neighbor {{ neighbor }} ebgp-multihop {{ config.ebgp_multihop }} +{% endif %} +{% if config.local_as is defined and config.local_as is not none %} +{% for local_asn in config.local_as %} + neighbor {{ neighbor }} local-as {{ local_asn }} {{ 'no-prepend' if config.local_as[local_asn].no_prepend is defined }} +{% endfor %} +{% endif %} +{% if config.override_capability is defined %} + neighbor {{ neighbor }} override-capability +{% endif %} +{% if config.passive is defined %} + neighbor {{ neighbor }} passive +{% endif %} +{% if config.password is defined and config.password is not none %} + neighbor {{ neighbor }} password {{ config.password }} +{% endif %} +{% if config.shutdown is defined %} + neighbor {{ neighbor }} shutdown +{% endif %} +{% if config.ttl_security is defined and config.ttl_security.hops is defined and config.ttl_security.hops is not none %} + neighbor {{ neighbor }} ttl-security hops {{ config.ttl_security.hops }} +{% endif %} +{% if config.update_source is defined and config.update_source is not none %} + neighbor {{ neighbor }} update-source {{ config.update_source }} +{% endif %} ! +{% if config.address_family is defined and config.address_family is not none %} +{% for af in config.address_family %} +{% if af == 'ipv4_unicast' %} address-family ipv4 unicast -{%- if 'aggregate_address' in bgp_afi[type] %} -{%- for ip in bgp_afi[type].aggregate_address %} -{%- if ( ('as_set' in bgp_afi[type].aggregate_address[ip]) and ('summary_only' in bgp_afi[type].aggregate_address[ip] ) ) %} - aggregate-address {{ ip }} as-set summary-only -{%- elif 'as_set' in bgp_afi[type].aggregate_address[ip] %} - aggregate-address {{ ip }} as-set -{%- elif 'summary_only' in bgp_afi[type].aggregate_address[ip] %} - aggregate-address {{ ip }} summary-only -{%- else %} - aggregate-address {{ ip }} -{%- endif %} -{%- endfor %} -{%- endif %} -{#- END aggregate address ipv4 #} - -{#- redistribute afi ipv4 #} -{%- if 'redistribute' in bgp_afi[type] %} -{%- for protocol in bgp_afi[type].redistribute %} -{%- if ( ('route_map' in bgp_afi[type].redistribute[protocol]) and ('metric' in bgp_afi[type].redistribute[protocol] ) ) %} - redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} -{%- elif 'metric' in bgp_afi[type].redistribute[protocol] %} - redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} -{%- elif 'route_map' in bgp_afi[type].redistribute[protocol] %} - redistribute {{protocol}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} -{%- elif 'table' in bgp_afi[type].redistribute %} - redistribute table {{bgp_afi[type].redistribute.table}} -{%- else %} - redistribute {{protocol}} -{%- endif %} -{%- endfor %} -{%- endif %} -{#- END redistribute #} - -{%- if 'network' in bgp_afi[type] %} -{%- for net in bgp_afi[type].network %} - network {{ net }} -{%- endfor %} -{%- endif %} - exit-address-family - ! -{%- endif %} - -{%- if type == "ipv6_unicast" %} - ! +{% elif af == 'ipv6_unicast' %} address-family ipv6 unicast -{%- if 'aggregate_address' in bgp_afi[type] %} -{%- for ip in bgp_afi[type].aggregate_address %} -{%- if ( ('as_set' in bgp_afi[type].aggregate_address[ip]) and ('summary_only' in bgp_afi[type].aggregate_address[ip] ) ) %} - aggregate-address {{ ip }} as-set summary-only -{%- elif 'as_set' in bgp_afi[type].aggregate_address[ip] %} - aggregate-address {{ ip }} as-set -{%- elif 'summary_only' in bgp_afi[type].aggregate_address[ip] %} - aggregate-address {{ ip }} summary-only -{%- else %} - aggregate-address {{ ip }} -{%- endif %} -{%- endfor %} -{%- endif %} -{#- END aggregate address ipv6 #} - -{#- redistribute afi ipv6 #} -{%- if 'redistribute' in bgp_afi[type] %} -{%- for protocol in bgp_afi[type].redistribute %} -{%- if ( ('route_map' in bgp_afi[type].redistribute[protocol]) and ('metric' in bgp_afi[type].redistribute[protocol] ) ) %} - redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} -{%- elif 'metric' in bgp_afi[type].redistribute[protocol] %} - redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} -{%- elif 'route_map' in bgp_afi[type].redistribute[protocol] %} - redistribute {{protocol}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} -{%- elif 'table' in bgp_afi[type].redistribute %} - redistribute table {{bgp_afi[type].redistribute.table}} -{%- else %} - redistribute {{protocol}} -{%- endif %} -{%- endfor %} -{%- endif %} -{#- END redistribute #} - -{%- if 'network' in bgp_afi[type] %} -{%- for net in bgp_afi[type].network %} - network {{ net }} -{%- endfor %} -{%- endif %} +{% endif %} +{% if config.address_family[af].allowas_in is defined and config.address_family[af].allowas_in is not none %} + neighbor {{ neighbor }} allowas-in {{ config.address_family[af].allowas_in.number if config.address_family[af].allowas_in.number is defined }} +{% endif %} +{% if config.address_family[af].remove_private_as is defined %} + neighbor {{ neighbor }} remove-private-AS +{% endif %} +{% if config.address_family[af].route_reflector_client is defined %} + neighbor {{ neighbor }} route-reflector-client +{% endif %} +{% if config.address_family[af].weight is defined and config.address_family[af].weight is not none %} + neighbor {{ neighbor }} weight {{ config.address_family[af].weight }} +{% endif %} +{% if config.address_family[af].attribute_unchanged is defined and config.address_family[af].attribute_unchanged is not none %} + neighbor {{ neighbor }} attribute-unchanged {{ 'as-path ' if config.address_family[af].attribute_unchanged.as_path is defined }}{{ 'med ' if config.address_family[af].attribute_unchanged.med is defined }}{{ 'next-hop ' if config.address_family[af].attribute_unchanged.next_hop is defined }} +{% endif %} +{% if config.address_family[af].capability is defined and config.address_family[af].capability.orf is defined and config.address_family[af].capability.orf.prefix_list is defined and config.address_family[af].capability.orf.prefix_list is not none %} + neighbor {{ neighbor }} capability orf prefix-list {{ config.address_family[af].capability.orf.prefix_list }} +{% endif %} +{% if config.address_family[af].default_originate is defined %} + neighbor {{ neighbor }} default-originate {{ 'route-map ' + config.address_family[af].default_originate.route_map if config.address_family[af].default_originate.route_map is defined }} +{% endif %} +{% 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 %} +{% 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 %} + neighbor {{ neighbor }} filter-list {{ config.address_family[af].filter_list.import }} in +{% endif %} +{% endif %} +{% if config.address_family[af].maximum_prefix is defined and config.address_family[af].maximum_prefix is not none %} + neighbor {{ neighbor }} maximum-prefix {{ config.address_family[af].maximum_prefix }} +{% endif %} +{% if config.address_family[af].nexthop_self is defined %} +{# https://phabricator.vyos.net/T1817 #} + neighbor {{ neighbor }} next-hop-self {{ 'force' if config.address_family[af].nexthop_self.force is defined }} +{% endif %} +{% if config.address_family[af].route_server_client is defined %} + neighbor {{ neighbor }} route-server-client +{% endif %} +{% 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 %} + 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 }} route-map {{ 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 %} + neighbor {{ neighbor }} route-map {{ config.address_family[af].prefix_list.export }} in +{% endif %} +{% endif %} +{% if config.address_family[af].soft_reconfiguration is defined and config.address_family[af].soft_reconfiguration.inbound is defined %} + neighbor {{ neighbor }} soft-reconfiguration inbound +{% endif %} +{% if config.address_family[af].unsuppress_map is defined and config.address_family[af].unsuppress_map is not none %} + neighbor {{ neighbor }} unsuppress-map {{ config.address_family[af].unsuppress_map }} +{% endif %} + neighbor {{ neighbor }} activate exit-address-family + ! +{% endfor %} +{% endif %} +{% endmacro %} ! -{%- endif %} -{%- endfor %} -{%- endif %} -{#- END Global ASN address-family section; set protocols bgp 65001 address-family #} - -{#- set protocols nbgp xxxx maximum-paths ibgp x, Generated by default for afi_4 #} -{#- We don't have this parameter in afi_6. But this is supported in the FRR #} -{%- if 'maximum_paths' in conf_bgp[asn] %} -{%- if 'ebgp' in conf_bgp[asn].maximum_paths %} +router bgp {{ asn }} + no bgp default ipv4-unicast +{% if address_family is defined and address_family is not none %} +{% for af in address_family %} ! +{% if af == 'ipv4_unicast' %} address-family ipv4 unicast - maximum-paths {{ conf_bgp[asn].maximum_paths.ebgp }} +{% elif af == 'ipv6_unicast' %} + address-family ipv6 unicast +{% endif %} +{% if address_family[af].aggregate_address is defined and address_family[af].aggregate_address is not none %} +{% for ip in address_family[af].aggregate_address %} + aggregate-address {{ ip }}{{ ' as-set' if address_family[af].aggregate_address[ip].as_set is defined }}{{ ' summary-only' if address_family[af].aggregate_address[ip].summary_only is defined }} +{% endfor %} +{% endif %} +{% if address_family[af].redistribute is defined and address_family[af].redistribute is not none %} +{% for protocol in address_family[af].redistribute %} +{% if protocol == 'table' %} + redistribute table {{ address_family[af].redistribute[protocol].table }} +{% else %} + redistribute {{ protocol }}{% if address_family[af].redistribute[protocol].metric is defined %} metric {{ address_family[af].redistribute[protocol].metric }}{% endif %}{% if address_family[af].redistribute[protocol].route_map is defined %} route-map {{ address_family[af].redistribute[protocol].route_map }}{% endif %} +{####### we need this blank line!! #######} + +{% endif %} +{% endfor %} +{% endif %} +{% if address_family[af].network is defined and address_family[af].network is not none %} +{% for network in address_family[af].network %} + network {{ network }}{% if address_family[af].network[network].route_map is defined %} route-map {{ address_family[af].network[network].route_map }}{% endif %}{% if address_family[af].network[network].backdoor is defined %} backdoor{% endif %} +{####### we need this blank line!! #######} + +{% endfor %} +{% endif %} exit-address-family +{% endfor %} +{% endif %} ! -{%- endif %} -{%- if 'ibgp' in conf_bgp[asn].maximum_paths %} +{# set protocols bgp xxxx maximum-paths ibgp x, Generated by default for afi_4 #} +{# We don't have this parameter in afi_6. But this is supported in FRR #} +{% if maximum_paths is defined and maximum_paths is not none %} +{% if maximum_paths.ebgp is defined and maximum_paths.ebgp is not none %} ! address-family ipv4 unicast - maximum-paths ibgp {{ conf_bgp[asn].maximum_paths.ibgp }} + maximum-paths {{ maximum_paths.ebgp }} exit-address-family ! -{%- endif %} -{%- endif %} - -{#- START peer-group; set protocol bgp xxx peer-group #} -{%- if 'peer_group' in conf_bgp[asn] %} -{%- for pr_group in conf_bgp[asn].peer_group %} -{%- set conf_peer_group = conf_bgp[asn].peer_group[pr_group] %} - neighbor {{pr_group}} peer-group - -{#- First parameter for peer-group - remote-as #} -{%- if 'remote_as' in conf_peer_group %} - neighbor {{ pr_group }} remote-as {{ conf_peer_group.remote_as }} -{%- endif %} - -{%- if 'bfd' in conf_peer_group %} - neighbor {{ pr_group }} bfd -{%- endif %} - -{%- if 'capability' in conf_peer_group %} -{%- if 'dynamic' in conf_peer_group.capability %} - neighbor {{ pr_group }} capability dynamic -{%- endif %} -{%- if 'extended_nexthop' in conf_peer_group.capability %} - neighbor {{ pr_group }} capability extended-nexthop -{%- endif %} -{%- endif %} - -{%- if 'description' in conf_peer_group %} - neighbor {{ pr_group }} description {{ conf_peer_group.description }} -{%- endif %} - -{%- if 'disable_capability_negotiation' in conf_peer_group %} - neighbor {{ pr_group }} disable-capability-negotiation -{%- endif %} - -{#- https://phabricator.vyos.net/T2844. 'disable-send-community' only for afi #} -{%- if 'disable_send_community' in conf_peer_group %} - ! -{%- endif %} - -{%- if 'ebgp_multihop' in conf_peer_group %} - neighbor {{ pr_group }} ebgp-multihop {{conf_peer_group.ebgp_multihop}} -{%- endif %} - -{%- if 'local_as' in conf_peer_group %} -{%- for loc_asn in conf_peer_group.local_as %} -{%- if 'no_prepend' in conf_peer_group.local_as[loc_asn] %} - neighbor {{ pr_group }} local-as {{loc_asn}} no-prepend -{%- else %} - neighbor {{ pr_group }} local-as {{loc_asn}} -{%- endif %} -{%- endfor %} -{%- endif %} - -{%- if 'override_capability' in conf_peer_group %} - neighbor {{ pr_group }} override-capability -{%- endif %} - -{%- if 'passive' in conf_peer_group %} - neighbor {{ pr_group }} passive -{%- endif %} - -{%- if 'password' in conf_peer_group %} - neighbor {{ pr_group }} password {{ conf_peer_group.password }} -{%- endif %} - -{%- if 'shutdown' in conf_peer_group %} - neighbor {{ pr_group }} shutdown -{%- endif %} - -{%- if 'ttl_security' in conf_peer_group %} -{%- if 'hops' in conf_peer_group.ttl_security %} - neighbor {{ pr_group }} ttl-security hops {{conf_peer_group.ttl_security.hops}} -{%- endif %} -{%- endif %} - -{%- if 'update_source' in conf_peer_group %} - neighbor {{ pr_group }} update-source {{ conf_peer_group.update_source }} -{%- endif %} - -{#- START peer-group afi; set protocols bgp xxx peer-group FOO address-family #} -{%- if 'address_family' in conf_peer_group %} -{%- for afi in conf_peer_group.address_family %} -{%- if afi == "ipv4_unicast" %} +{% endif %} +{% if maximum_paths.ibgp is defined and maximum_paths.ibgp is not none %} ! address-family ipv4 unicast - -{%- if 'allowas_in' in conf_peer_group.address_family.ipv4_unicast %} -{%- if 'number' in conf_peer_group.address_family.ipv4_unicast.allowas_in %} - neighbor {{ pr_group }} allowas-in {{ conf_peer_group.address_family.ipv4_unicast.allowas_in.number }} -{%- else %} - neighbor {{ pr_group }} allowas-in -{%- endif %} -{%- endif %} - -{#- START Single Params for peer-group; set protocols bgp xxx peer-group FOO address-family ipv4-unicast #} - -{%- if 'remove_private_as' in conf_peer_group.address_family.ipv4_unicast %} - neighbor {{ pr_group }} remove-private-AS -{%- endif %} - -{%- if 'route_reflector_client' in conf_peer_group.address_family.ipv4_unicast %} - neighbor {{ pr_group }} route-reflector-client -{%- endif %} - -{%- if 'weight' in conf_peer_group.address_family.ipv4_unicast %} - neighbor {{ pr_group }} weight {{ conf_peer_group.address_family.ipv4_unicast.weight }} -{%- endif %} -{#- END single params for peer-group #} - -{%- if 'attribute_unchanged' in conf_peer_group.address_family.ipv4_unicast %} -{%- if ( ('as_path' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) and ('med' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) ) %} - neighbor {{ pr_group }} attribute-unchanged as-path med -{%- elif ( ('as_path' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) and ('next_hop' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) ) %} - neighbor {{ pr_group }} attribute-unchanged as-path next-hop -{%- elif ( ('med' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) and ('next_hop' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) ) %} - neighbor {{ pr_group }} attribute-unchanged med next-hop -{%- elif 'as_path' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged %} - neighbor {{ pr_group }} attribute-unchanged as-path -{%- elif 'med' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged %} - neighbor {{ pr_group }} attribute-unchanged med -{%- elif 'next_hop' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged %} - neighbor {{ pr_group }} attribute-unchanged next-hop -{%- else %} - neighbor {{ pr_group }} attribute-unchanged as-path next-hop med -{%- endif %} -{%- endif %} -{#- END attribute-unchanged #} - -{%- if 'capability' in conf_peer_group.address_family.ipv4_unicast %} -{%- if 'orf' in conf_peer_group.address_family.ipv4_unicast.capability %} -{%- if 'receive' in conf_peer_group.address_family.ipv4_unicast.capability.orf.prefix_list %} - neighbor {{ pr_group }} capability orf prefix-list receive -{%- endif %} -{%- if 'send' in conf_peer_group.address_family.ipv4_unicast.capability.orf.prefix_list %} - neighbor {{ pr_group }} capability orf prefix-list send -{%- endif %} -{%- endif %} -{%- endif %} - -{%- if 'default_originate' in conf_peer_group.address_family.ipv4_unicast %} -{%- if 'route_map' in conf_peer_group.address_family.ipv4_unicast.default_originate %} - neighbor {{ pr_group }} default-originate route-map {{ conf_peer_group.address_family.ipv4_unicast.default_originate.route_map }} -{%- else %} - neighbor {{ pr_group }} default-originate -{%- endif %} -{%- endif %} - -{%- if 'distribute_list' in conf_peer_group.address_family.ipv4_unicast %} -{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.distribute_list %} - neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv4_unicast.distribute_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.distribute_list %} - neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv4_unicast.distribute_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'filter_list' in conf_peer_group.address_family.ipv4_unicast %} -{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.filter_list %} - neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv4_unicast.filter_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.filter_list %} - neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv4_unicast.filter_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'maximum_prefix' in conf_peer_group.address_family.ipv4_unicast %} - neighbor {{ pr_group }} maximum-prefix {{ conf_peer_group.address_family.ipv4_unicast.maximum_prefix }} -{%- endif %} - -{#- https://phabricator.vyos.net/T1817 #} -{%- if 'nexthop_self' in conf_peer_group.address_family.ipv4_unicast %} -{%- if 'force' in conf_peer_group.address_family.ipv4_unicast.nexthop_self %} - neighbor {{ pr_group }} next-hop-self force - neighbor {{ pr_group }} next-hop-self -{%- else %} - neighbor {{ pr_group }} next-hop-self -{%- endif %} -{%- endif %} - -{%- if 'route_server_client' in conf_peer_group.address_family.ipv4_unicast %} - neighbor {{ pr_group }} route-server-client -{%- endif %} - -{%- if 'route_map' in conf_peer_group.address_family.ipv4_unicast %} -{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.route_map %} - neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv4_unicast.route_map.export}} out -{%- endif %} -{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.route_map %} - neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv4_unicast.route_map.import}} in -{%- endif %} -{%- endif %} -{%- if 'prefix_list' in conf_peer_group.address_family.ipv4_unicast %} -{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.prefix_list %} - neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv4_unicast.prefix_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.prefix_list %} - neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv4_unicast.prefix_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'soft_reconfiguration' in conf_peer_group.address_family.ipv4_unicast %} -{%- if 'inbound' is defined %} - neighbor {{ pr_group }} soft-reconfiguration inbound -{%- endif %} -{%- endif %} - -{%- if 'unsuppress_map' in conf_peer_group.address_family.ipv4_unicast %} - neighbor {{ pr_group }} unsuppress-map {{conf_peer_group.address_family.ipv4_unicast.unsuppress_map}} -{%- endif %} - neighbor {{ pr_group }} activate + maximum-paths ibgp {{ maximum_paths.ibgp }} exit-address-family ! -{%- endif %} - -{%- if afi == "ipv6_unicast" %} +{% endif %} +{% endif %} ! - address-family ipv6 unicast - -{%- if 'allowas_in' in conf_peer_group.address_family.ipv6_unicast %} -{%- if 'number' in conf_peer_group.address_family.ipv6_unicast.allowas_in %} - neighbor {{ pr_group }} allowas-in {{ conf_peer_group.address_family.ipv6_unicast.allowas_in.number }} -{%- else %} - neighbor {{ pr_group }} allowas-in -{%- endif %} -{%- endif %} - -{#- START Single Params for peer-group afi6; set protocols bgp xxx peer-group FOO address-family ipv6-unicast #} -{%- if 'remove_private_as' in conf_peer_group.address_family.ipv6_unicast %} - neighbor {{ pr_group }} remove-private-AS -{%- endif %} - -{%- if 'route_reflector_client' in conf_peer_group.address_family.ipv6_unicast %} - neighbor {{ pr_group }} route-reflector-client -{%- endif %} - -{%- if 'weight' in conf_peer_group.address_family.ipv6_unicast %} - neighbor {{ pr_group }} weight {{ conf_peer_group.address_family.ipv6_unicast.weight }} -{%- endif %} -{#- END single params for peer-group afi6 #} - -{%- if 'attribute_unchanged' in conf_peer_group.address_family.ipv6_unicast %} -{%- if ( ('as_path' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) and ('med' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) ) %} - neighbor {{ pr_group }} attribute-unchanged as-path med -{%- elif ( ('as_path' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) and ('next_hop' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) ) %} - neighbor {{ pr_group }} attribute-unchanged as-path next-hop -{%- elif ( ('med' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) and ('next_hop' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) ) %} - neighbor {{ pr_group }} attribute-unchanged med next-hop -{%- elif 'as_path' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged %} - neighbor {{ pr_group }} attribute-unchanged as-path -{%- elif 'med' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged %} - neighbor {{ pr_group }} attribute-unchanged med -{%- elif 'next_hop' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged %} - neighbor {{ pr_group }} attribute-unchanged next-hop -{%- else %} - neighbor {{ pr_group }} attribute-unchanged as-path next-hop med -{%- endif %} -{%- endif %} -{#- END attribute-unchanged ipv6 #} - -{%- if 'capability' in conf_peer_group.address_family.ipv6_unicast %} -{%- if 'dynamic' in conf_peer_group.address_family.ipv6_unicast.capability %} -{#- exit from afi ipv6 unicast because 'dynamic' its a global parameter for peer-group in afi6. Other checks are ongoing in afi6. Also related T3037 #} - exit-address-family - neighbor {{ pr_group }} capability dynamic - address-family ipv6 unicast -{%- endif %} -{%- if 'orf' in conf_peer_group.address_family.ipv6_unicast.capability %} -{%- if 'receive' in conf_peer_group.address_family.ipv6_unicast.capability.orf.prefix_list %} - neighbor {{ pr_group }} capability orf prefix-list receive -{%- endif %} -{%- if 'send' in conf_peer_group.address_family.ipv6_unicast.capability.orf.prefix_list %} - neighbor {{ pr_group }} capability orf prefix-list send -{%- endif %} -{%- endif %} -{%- endif %} - -{%- if 'default_originate' in conf_peer_group.address_family.ipv6_unicast %} -{%- if 'route_map' in conf_peer_group.address_family.ipv6_unicast.default_originate %} - neighbor {{ pr_group }} default-originate route-map {{ conf_peer_group.address_family.ipv6_unicast.default_originate.route_map }} -{%- else %} - neighbor {{ pr_group }} default-originate -{%- endif %} -{%- endif %} - -{%- if 'distribute_list' in conf_peer_group.address_family.ipv6_unicast %} -{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.distribute_list %} - neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv6_unicast.distribute_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.distribute_list %} - neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv6_unicast.distribute_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'filter_list' in conf_peer_group.address_family.ipv6_unicast %} -{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.filter_list %} - neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv6_unicast.filter_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.filter_list %} - neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv6_unicast.filter_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'maximum_prefix' in conf_peer_group.address_family.ipv6_unicast %} - neighbor {{ pr_group }} maximum-prefix {{ conf_peer_group.address_family.ipv6_unicast.maximum_prefix }} -{%- endif %} - -{#- https://phabricator.vyos.net/T1817 #} -{%- if 'nexthop_self' in conf_peer_group.address_family.ipv6_unicast %} -{%- if 'force' in conf_peer_group.address_family.ipv6_unicast.nexthop_self %} - neighbor {{ pr_group }} next-hop-self force - neighbor {{ pr_group }} next-hop-self -{%- else %} - neighbor {{ pr_group }} next-hop-self -{%- endif %} -{%- endif %} - -{%- if 'route_server_client' in conf_peer_group.address_family.ipv6_unicast %} - neighbor {{ pr_group }} route-server-client -{%- endif %} - -{%- if 'route_map' in conf_peer_group.address_family.ipv6_unicast %} -{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.route_map %} - neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv6_unicast.route_map.export}} out -{%- endif %} -{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.route_map %} - neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv6_unicast.route_map.import}} in -{%- endif %} -{%- endif %} -{%- if 'prefix_list' in conf_peer_group.address_family.ipv6_unicast %} -{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.prefix_list %} - neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv6_unicast.prefix_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.prefix_list %} - neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv6_unicast.prefix_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'soft_reconfiguration' in conf_peer_group.address_family.ipv6_unicast %} -{%- if 'inbound' is defined %} - neighbor {{ pr_group }} soft-reconfiguration inbound -{%- endif %} -{%- endif %} - -{%- if 'unsuppress_map' in conf_peer_group.address_family.ipv6_unicast %} - neighbor {{ pr_group }} unsuppress-map {{conf_peer_group.address_family.ipv6_unicast.unsuppress_map}} -{%- endif %} - neighbor {{ pr_group }} activate - exit-address-family +{% if peer_group is defined and peer_group is not none %} +{% for peer, config in peer_group.items() %} +{{ bgp_neighbor(peer, config, true) }} +{% endfor %} +{% endif %} ! -{%- endif %} - -{%- endfor %} -{%- endif %} -{#- END peer-group afi; set protocols bgp xxx peer-group FOO address-family #} - -{%- endfor %} -{%- endif %} -{#- END peer-group; set protocol bgp xxx peer-group #} - -{#- START peer section; set protocol bgp xxx neighbor #} -{%- for peer in conf_bgp[asn].neighbor %} -{#- set peer-group as conf_peer #} -{%- set conf_peer = conf_bgp[asn].neighbor[peer] %} - -{#- First parameter for peer neighbor - remote-as #} -{%- if 'remote_as' in conf_peer %} - neighbor {{ peer }} remote-as {{ conf_peer.remote_as }} -{%- endif %} - -{%- if 'advertisement_interval' in conf_peer %} - neighbor {{ peer }} advertisement-interval {{ conf_peer.advertisement_interval }} -{%- endif %} - -{%- if 'bfd' in conf_peer %} -{%- if 'check_control_plane_failure' in conf_peer.bfd %} - neighbor {{ peer }} bfd - neighbor {{ peer }} bfd check-control-plane-failure -{%- else %} - neighbor {{ peer }} bfd -{%- endif %} -{%- endif %} - -{%- if 'capability' in conf_peer %} -{%- if 'dynamic' in conf_peer.capability %} - neighbor {{ peer }} capability dynamic -{%- endif %} -{%- if 'extended_nexthop' in conf_peer.capability %} - neighbor {{ peer }} capability extended-nexthop -{%- endif %} -{%- endif %} - -{%- if 'disable_capability_negotiation' in conf_peer %} - neighbor {{ peer }} disable-capability-negotiation -{%- endif %} - -{#- https://phabricator.vyos.net/T2844. 'disable-send-community' only for afi #} -{%- if 'disable_send_community' in conf_peer %} +{% if neighbor is defined and neighbor is not none %} +{% for n, config in neighbor.items() %} +{{ bgp_neighbor(n, config) }} +{% endfor %} +{% endif %} ! -{%- endif %} - -{%- if 'ebgp_multihop' in conf_peer %} - neighbor {{ peer }} ebgp-multihop {{conf_peer.ebgp_multihop}} -{%- endif %} - -{#- Need to check. 'Peer-group' needs to define before this section #} -{%- if 'interface' in conf_peer %} -{%- if 'peer_group' in conf_peer.interface %} - neighbor {{ peer }} interface peer-group {{conf_peer.interface.peer_group}} -{%- endif %} -{%- if 'remote_as' in conf_peer.interface %} - neighbor {{ peer }} interface remote-as {{conf_peer.interface.remote_as}} -{%- endif %} -{%- if 'v6only' in conf_peer.interface %} -{%- if 'peer_group' in conf_peer.interface.v6only %} - neighbor {{ peer }} peer-group {{conf_peer.interface.peer_group}} -{%- endif %} -{%- if 'remote_as' in conf_peer.interface.v6only %} - neighbor {{ peer }} interface v6only remote-as {{conf_peer.interface.v6only.remote_as}} -{%- endif %} -{%- endif %} -{%- endif %} - -{%- if 'local_as' in conf_peer %} -{%- for loc_asn in conf_peer.local_as %} -{%- if 'no_prepend' in conf_peer.local_as[loc_asn] %} - neighbor {{ peer }} local-as {{loc_asn}} no-prepend -{%- else %} - neighbor {{ peer }} local-as {{loc_asn}} -{%- endif %} -{%- endfor %} -{%- endif %} - -{%- if 'override_capability' in conf_peer %} - neighbor {{ peer }} override-capability -{%- endif %} - -{%- if 'passive' in conf_peer %} - neighbor {{ peer }} passive -{%- endif %} - -{%- if 'password' in conf_peer %} - neighbor {{ peer }} password {{ conf_peer.password }} -{%- endif %} - -{%- if 'peer_group' in conf_peer %} - neighbor {{ peer }} peer-group {{ conf_peer.peer_group }} -{%- endif %} - -{%- if 'port' in conf_peer %} - neighbor {{ peer }} port {{ conf_peer.port }} -{%- endif %} - -{%- if 'shutdown' in conf_peer %} - neighbor {{ peer }} shutdown -{%- endif %} - -{%- if 'strict_capability_match' in conf_peer %} - neighbor {{ peer }} strict-capability-match -{%- endif %} - -{#- set protocols bgp xxx neighbor x.x.x.x timers #} -{%- if 'timers' in conf_peer %} -{%- if ( ('connect' in conf_peer.timers) and ('holdtime' in conf_peer.timers) and ('keepalive' in conf_peer.timers ) ) %} - neighbor {{ peer }} timers {{conf_peer.timers.keepalive}} {{conf_peer.timers.holdtime}} - neighbor {{ peer }} timers connect {{conf_peer.timers.connect}} -{%- elif ( ('holdtime' in conf_peer.timers) and ('keepalive' in conf_peer.timers ) ) %} - neighbor {{ peer }} timers {{conf_peer.timers.keepalive}} {{conf_peer.timers.holdtime}} -{%- elif 'connect' in conf_peer.timers %} - neighbor {{ peer }} timers connect {{conf_peer.timers.connect}} -{%- endif %} -{%- endif %} - -{%- if 'ttl_security' in conf_peer %} -{%- if 'hops' in conf_peer.ttl_security %} - neighbor {{ peer }} ttl-security hops {{conf_peer.ttl_security.hops}} -{%- endif %} -{%- endif %} - -{%- if 'update_source' in conf_peer %} - neighbor {{ peer }} update-source {{ conf_peer.update_source }} -{%- endif %} - -{%- if 'description' in conf_peer %} - neighbor {{ peer }} description {{ conf_peer.description }} -{%- endif %} - -{#- START address family for peer; set protocols bgp xxx neighbor x.x.x.x address-family ipvX-unicast #} -{%- if 'address_family' in conf_peer %} -{%- for afi in conf_peer.address_family %} -{%- if afi == "ipv4_unicast" %} - ! - address-family ipv4 unicast - -{%- if 'allowas_in' in conf_peer.address_family.ipv4_unicast %} -{%- if 'number' in conf_peer.address_family.ipv4_unicast.allowas_in %} - neighbor {{ peer }} allowas-in {{ conf_peer.address_family.ipv4_unicast.allowas_in.number }} -{%- else %} - neighbor {{ peer }} allowas-in -{%- endif %} -{%- endif %} - -{#- START Single Params for neighbor; #} -{%- if 'as_override' in conf_peer.address_family.ipv4_unicast %} - neighbor {{ peer }} as-override -{%- endif %} - -{%- if 'remove_private_as' in conf_peer.address_family.ipv4_unicast %} - neighbor {{ peer }} remove-private-AS -{%- endif %} - -{%- if 'route_reflector_client' in conf_peer.address_family.ipv4_unicast %} - neighbor {{ peer }} route-reflector-client -{%- endif %} - -{%- if 'weight' in conf_peer.address_family.ipv4_unicast %} - neighbor {{ peer }} weight {{ conf_peer.address_family.ipv4_unicast.weight }} -{%- endif %} -{#- END single params for neighbor #} - -{%- if 'attribute_unchanged' in conf_peer.address_family.ipv4_unicast %} -{%- if ( ('as_path' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) and ('med' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) ) %} - neighbor {{ peer }} attribute-unchanged as-path med -{%- elif ( ('as_path' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) and ('next_hop' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) ) %} - neighbor {{ peer }} attribute-unchanged as-path next-hop -{%- elif ( ('med' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) and ('next_hop' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) ) %} - neighbor {{ peer }} attribute-unchanged med next-hop -{%- elif 'as_path' in conf_peer.address_family.ipv4_unicast.attribute_unchanged %} - neighbor {{ peer }} attribute-unchanged as-path -{%- elif 'med' in conf_peer.address_family.ipv4_unicast.attribute_unchanged %} - neighbor {{ peer }} attribute-unchanged med -{%- elif 'next_hop' in conf_peer.address_family.ipv4_unicast.attribute_unchanged %} - neighbor {{ peer }} attribute-unchanged next-hop -{%- else %} - neighbor {{ peer }} attribute-unchanged as-path next-hop med -{%- endif %} -{%- endif %} -{#- END attribute-unchanged #} - -{%- if 'capability' in conf_peer.address_family.ipv4_unicast %} -{%- if 'orf' in conf_peer.address_family.ipv4_unicast.capability %} -{%- if 'receive' in conf_peer.address_family.ipv4_unicast.capability.orf.prefix_list %} - neighbor {{ peer }} capability orf prefix-list receive -{%- endif %} -{%- if 'send' in conf_peer.address_family.ipv4_unicast.capability.orf.prefix_list %} - neighbor {{ peer }} capability orf prefix-list send -{%- endif %} -{%- endif %} -{%- endif %} - -{%- if 'default_originate' in conf_peer.address_family.ipv4_unicast %} -{%- if 'route_map' in conf_peer.address_family.ipv4_unicast.default_originate %} - neighbor {{ peer }} default-originate route-map {{ conf_peer.address_family.ipv4_unicast.default_originate.route_map }} -{%- else %} - neighbor {{ peer }} default-originate -{%- endif %} -{%- endif %} - -{%- if 'distribute_list' in conf_peer.address_family.ipv4_unicast %} -{%- if 'export' in conf_peer.address_family.ipv4_unicast.distribute_list %} - neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv4_unicast.distribute_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer.address_family.ipv4_unicast.distribute_list %} - neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv4_unicast.distribute_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'filter_list' in conf_peer.address_family.ipv4_unicast %} -{%- if 'export' in conf_peer.address_family.ipv4_unicast.filter_list %} - neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv4_unicast.filter_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer.address_family.ipv4_unicast.filter_list %} - neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv4_unicast.filter_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'maximum_prefix' in conf_peer.address_family.ipv4_unicast %} - neighbor {{ peer }} maximum-prefix {{ conf_peer.address_family.ipv4_unicast.maximum_prefix }} -{%- endif %} - -{#- https://phabricator.vyos.net/T1817 #} -{%- if 'nexthop_self' in conf_peer.address_family.ipv4_unicast %} -{%- if 'force' in conf_peer.address_family.ipv4_unicast.nexthop_self %} - neighbor {{ peer }} next-hop-self force - neighbor {{ peer }} next-hop-self -{%- else %} - neighbor {{ peer }} next-hop-self -{%- endif %} -{%- endif %} - -{%- if 'route_server_client' in conf_peer.address_family.ipv4_unicast %} - neighbor {{ peer }} route-server-client -{%- endif %} - -{%- if 'route_map' in conf_peer.address_family.ipv4_unicast %} -{%- if 'export' in conf_peer.address_family.ipv4_unicast.route_map %} - neighbor {{ peer }} route-map {{conf_peer.address_family.ipv4_unicast.route_map.export}} out -{%- endif %} -{%- if 'import' in conf_peer.address_family.ipv4_unicast.route_map %} - neighbor {{ peer }} route-map {{conf_peer.address_family.ipv4_unicast.route_map.import}} in -{%- endif %} -{%- endif %} -{%- if 'prefix_list' in conf_peer.address_family.ipv4_unicast %} -{%- if 'export' in conf_peer.address_family.ipv4_unicast.prefix_list %} - neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv4_unicast.prefix_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer.address_family.ipv4_unicast.prefix_list %} - neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv4_unicast.prefix_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'soft_reconfiguration' in conf_peer.address_family.ipv4_unicast %} -{%- if 'inbound' is defined %} - neighbor {{ peer }} soft-reconfiguration inbound -{%- endif %} -{%- endif %} - -{%- if 'unsuppress_map' in conf_peer.address_family.ipv4_unicast %} - neighbor {{ peer }} unsuppress-map {{conf_peer.address_family.ipv4_unicast.unsuppress_map}} -{%- endif %} - neighbor {{ peer }} activate - exit-address-family - ! -{%- endif %} - -{%- if afi == "ipv6_unicast" %} - ! - address-family ipv6 unicast - -{%- if 'allowas_in' in conf_peer.address_family.ipv6_unicast %} -{%- if 'number' in conf_peer.address_family.ipv6_unicast.allowas_in %} - neighbor {{ peer }} allowas-in {{ conf_peer.address_family.ipv6_unicast.allowas_in.number }} -{%- else %} - neighbor {{ peer }} allowas-in -{%- endif %} -{%- endif %} - -{#- START Single Params for neighbor #} -{%- if 'as_override' in conf_peer.address_family.ipv6_unicast %} - neighbor {{ peer }} as-override -{%- endif %} - -{%- if 'remove_private_as' in conf_peer.address_family.ipv6_unicast %} - neighbor {{ peer }} remove-private-AS -{%- endif %} - -{%- if 'route_reflector_client' in conf_peer.address_family.ipv6_unicast %} - neighbor {{ peer }} route-reflector-client -{%- endif %} - -{%- if 'weight' in conf_peer.address_family.ipv6_unicast %} - neighbor {{ peer }} weight {{ conf_peer.address_family.ipv6_unicast.weight }} -{%- endif %} -{#- END single params for neighbor #} - -{%- if 'attribute_unchanged' in conf_peer.address_family.ipv6_unicast %} -{%- if ( ('as_path' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) and ('med' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) ) %} - neighbor {{ peer }} attribute-unchanged as-path med -{%- elif ( ('as_path' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) and ('next_hop' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) ) %} - neighbor {{ peer }} attribute-unchanged as-path next-hop -{%- elif ( ('med' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) and ('next_hop' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) ) %} - neighbor {{ peer }} attribute-unchanged med next-hop -{%- elif 'as_path' in conf_peer.address_family.ipv6_unicast.attribute_unchanged %} - neighbor {{ peer }} attribute-unchanged as-path -{%- elif 'med' in conf_peer.address_family.ipv6_unicast.attribute_unchanged %} - neighbor {{ peer }} attribute-unchanged med -{%- elif 'next_hop' in conf_peer.address_family.ipv6_unicast.attribute_unchanged %} - neighbor {{ peer }} attribute-unchanged next-hop -{%- else %} - neighbor {{ peer }} attribute-unchanged as-path next-hop med -{%- endif %} -{%- endif %} -{#- END attribute-unchanged #} - -{%- if 'capability' in conf_peer.address_family.ipv6_unicast %} -{%- if 'orf' in conf_peer.address_family.ipv6_unicast.capability %} -{%- if 'receive' in conf_peer.address_family.ipv6_unicast.capability.orf.prefix_list %} - neighbor {{ peer }} capability orf prefix-list receive -{%- endif %} -{%- if 'send' in conf_peer.address_family.ipv6_unicast.capability.orf.prefix_list %} - neighbor {{ peer }} capability orf prefix-list send -{%- endif %} -{%- endif %} -{%- endif %} - -{%- if 'default_originate' in conf_peer.address_family.ipv6_unicast %} -{%- if 'route_map' in conf_peer.address_family.ipv6_unicast.default_originate %} - neighbor {{ peer }} default-originate route-map {{ conf_peer.address_family.ipv6_unicast.default_originate.route_map }} -{%- else %} - neighbor {{ peer }} default-originate -{%- endif %} -{%- endif %} - -{%- if 'distribute_list' in conf_peer.address_family.ipv6_unicast %} -{%- if 'export' in conf_peer.address_family.ipv6_unicast.distribute_list %} - neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv6_unicast.distribute_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer.address_family.ipv6_unicast.distribute_list %} - neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv6_unicast.distribute_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'filter_list' in conf_peer.address_family.ipv6_unicast %} -{%- if 'export' in conf_peer.address_family.ipv6_unicast.filter_list %} - neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv6_unicast.filter_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer.address_family.ipv6_unicast.filter_list %} - neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv6_unicast.filter_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'maximum_prefix' in conf_peer.address_family.ipv6_unicast %} - neighbor {{ peer }} maximum-prefix {{ conf_peer.address_family.ipv6_unicast.maximum_prefix }} -{%- endif %} - -{#- https://phabricator.vyos.net/T1817 #} -{%- if 'nexthop_self' in conf_peer.address_family.ipv6_unicast %} -{%- if 'force' in conf_peer.address_family.ipv6_unicast.nexthop_self %} - neighbor {{ peer }} next-hop-self force - neighbor {{ peer }} next-hop-self -{%- else %} - neighbor {{ peer }} next-hop-self -{%- endif %} -{%- endif %} - -{%- if 'route_server_client' in conf_peer.address_family.ipv6_unicast %} - neighbor {{ peer }} route-server-client -{%- endif %} - -{%- if 'route_map' in conf_peer.address_family.ipv6_unicast %} -{%- if 'export' in conf_peer.address_family.ipv6_unicast.route_map %} - neighbor {{ peer }} route-map {{conf_peer.address_family.ipv6_unicast.route_map.export}} out -{%- endif %} -{%- if 'import' in conf_peer.address_family.ipv6_unicast.route_map %} - neighbor {{ peer }} route-map {{conf_peer.address_family.ipv6_unicast.route_map.import}} in -{%- endif %} -{%- endif %} -{%- if 'prefix_list' in conf_peer.address_family.ipv6_unicast %} -{%- if 'export' in conf_peer.address_family.ipv6_unicast.prefix_list %} - neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv6_unicast.prefix_list.export}} out -{%- endif %} -{%- if 'import' in conf_peer.address_family.ipv6_unicast.prefix_list %} - neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv6_unicast.prefix_list.import}} in -{%- endif %} -{%- endif %} - -{%- if 'soft_reconfiguration' in conf_peer.address_family.ipv6_unicast %} -{%- if 'inbound' is defined %} - neighbor {{ peer }} soft-reconfiguration inbound -{%- endif %} -{%- endif %} - -{%- if 'unsuppress_map' in conf_peer.address_family.ipv6_unicast %} - neighbor {{ peer }} unsuppress-map {{conf_peer.address_family.ipv6_unicast.unsuppress_map}} -{%- endif %} - neighbor {{ peer }} activate - exit-address-family - ! -{%- endif %} - -{%- endfor %} -{%- endif %} -{#- END address family for peer #} - -{%- endfor %} -{#- END peer section; set protocols bgp xxx neighbor #} - -{#- START parameters section; set protocol bgp xxx parameters #} -{%- if 'always_compare_med' in bgp_params %} +{% if parameters is defined %} +{% if parameters.always_compare_med is defined %} bgp always-compare-med -{%- endif %} - -{%- if 'bestpath' in bgp_params %} -{%- if 'compare_routerid' in bgp_params.bestpath %} +{% endif %} +{% if parameters.bestpath is defined and parameters.bestpath is not none %} +{% if parameters.bestpath.compare_routerid is defined %} bgp bestpath compare-routerid -{%- endif %} -{%- if 'as_path' in bgp_params.bestpath %} -{%- if 'confed' in bgp_params.bestpath.as_path %} - bgp bestpath as-path confed -{%- endif %} -{%- if 'ignore' in bgp_params.bestpath.as_path %} - bgp bestpath as-path ignore -{%- endif %} -{%- if 'multipath_relax' in bgp_params.bestpath.as_path %} - bgp bestpath as-path multipath-relax -{%- endif %} -{%- endif %} -{%- if 'med' in bgp_params.bestpath %} -{%- if ( ('confed' in bgp_params.bestpath.med) and ('missing_as_worst' in bgp_params.bestpath.med ) ) %} - bgp bestpath med confed missing-as-worst -{%- elif 'confed' in bgp_params.bestpath.med %} - bgp bestpath med confed -{%- elif 'missing_as_worst' in bgp_params.bestpath.med %} - bgp bestpath med missing-as-worst -{%- endif%} -{%- endif %} -{%- endif %} - -{%- if 'cluster_id' in bgp_params %} - bgp cluster-id {{ bgp_params.cluster_id }} -{%- endif %} - -{%- if 'confederation' in bgp_params %} -{%- if 'identifier' in bgp_params.confederation %} - bgp confederation identifier {{ bgp_params.confederation.identifier }} -{%- endif %} -{%- if 'peers' in bgp_params.confederation %} - bgp confederation peers {{ bgp_params.confederation.peers }} -{%- endif %} -{%- endif %} - -{#- Doesn't work in current FRR configuration; vtysh (bgp dampening 16 751 2001 61) #} -{%- if 'dampening' in bgp_params %} -{%- if ( ('half_life' in bgp_params.dampening) and ('max_suppress_time' in bgp_params.dampening) and ('re_use' in bgp_params.dampening) and ('start_suppress_time' in bgp_params.dampening ) ) %} - bgp dampening {{ bgp_params.dampening.half_life }} {{ bgp_params.dampening.re_use }} {{ bgp_params.dampening.start_suppress_time }} {{ bgp_params.dampening.max_suppress_time }} -{%- endif %} -{%- endif %} - -{%- if 'default' in bgp_params %} -{%- if 'local_pref' in bgp_params.default %} - bgp default local-preference {{ bgp_params.default.local_pref }} -{%- endif %} -{#- We use this is parameter as default in template (5-th string) #} -{%- if 'no_ipv4_unicast' in bgp_params.default %} +{% endif %} +{% if parameters.bestpath.as_path is defined and parameters.bestpath.as_path is not none %} +{% for option in parameters.bestpath.as_path %} + bgp bestpath as-path {{ option|replace('_', '-') }} +{% endfor %} +{% endif %} +{% if parameters.bestpath.med is defined and parameters.bestpath.med is not none %} + bgp bestpath med {{ 'confed' if parameters.bestpath.med.confed is defined }} {{ 'missing-as-worst' if parameters.bestpath.med.missing_as_worst is defined }} +{% endif %} +{% endif %} +{% if parameters.cluster_id is defined and parameters.cluster_id is not none %} + bgp cluster-id {{ parameters.cluster_id }} +{% endif %} +{% if parameters.confederation is defined and parameters.confederation is not none %} +{% if parameters.confederation.identifier is defined and parameters.confederation.identifier is not none %} + bgp confederation identifier {{ parameters.confederation.identifier }} +{% endif %} +{% if parameters.confederation.peers is defined and parameters.confederation.peers is not none %} + bgp confederation peers {{ parameters.confederation.peers }} +{% endif %} +{% endif %} +{% if parameters.dampening is defined and parameters.dampening is defined and parameters.dampening.half_life is defined and parameters.dampening.half_life is not none %} +{# Doesn't work in current FRR configuration; vtysh (bgp dampening 16 751 2001 61) #} + bgp dampening {{ parameters.dampening.half_life }} {{ parameters.dampening.re_use if parameters.dampening.re_use is defined }} {{ parameters.dampening.start_suppress_time if parameters.dampening.start_suppress_time is defined }} {{ parameters.dampening.max_suppress_time if parameters.dampening.max_suppress_time is defined }} +{% endif %} +{% if parameters.default is defined and parameters.default is not none %} +{% if parameters.default.local_pref is defined and parameters.default.local_pref is not none %} + bgp default local-preference {{ parameters.default.local_pref }} +{% endif %} +{% if parameters.default.no_ipv4_unicast is defined %} +{# We use this is parameter as default in template (5-th string) #} no bgp default ipv4-unicast -{%- endif %} -{%- endif %} - -{%- if 'deterministic_med' in bgp_params %} - bgp deterministic-med -{%- endif %} - -{%- if 'distance' in bgp_params %} -{%- if 'global' in bgp_params.distance %} -{%- if ( ('external' in bgp_params.distance.global) and ('internal' in bgp_params.distance.global) and ('local' in bgp_params.distance.global ) ) %} +{% endif %} +{% endif %} +{% if parameters.deterministic_med is defined %} + bgp deterministic-med +{% endif %} +{% if parameters.distance is defined and parameters.distance is not none %} ! address-family ipv4 unicast - distance bgp {{ bgp_params.distance.global.external }} {{ bgp_params.distance.global.internal }} {{ bgp_params.distance.global.local }} +{% if parameters.distance.global is defined and parameters.distance.global.external is defined and parameters.distance.global.internal is defined and parameters.distance.global.local is defined %} + distance bgp {{ parameters.distance.global.external }} {{ parameters.distance.global.internal }} {{ parameters.distance.global.local }} +{% endif %} +{% if parameters.distance.prefix is defined and parameters.distance.prefix is not none %} +{% for prefix in parameters.distance.prefix %} + distance {{ parameters.distance.prefix[prefix].distance }} {{ prefix }} +{% endfor %} +{% endif %} exit-address-family -! -{%- endif %} -{%- endif %} -{%- if 'prefix' in bgp_params.distance %} ! - address-family ipv4 unicast -{%- for prfx in bgp_params.distance.prefix %} - distance {{ bgp_params.distance.prefix[prfx].distance }} {{ prfx }} -{%- endfor %} - exit-address-family -! -{%- endif %} -{%- endif %} - -{%- if 'graceful_restart' in bgp_params %} -{%- if 'stalepath_time' in bgp_params.graceful_restart %} - bgp graceful-restart stalepath-time {{ bgp_params.graceful_restart.stalepath_time }} -{%- endif %} -{%- endif %} - -{%- if 'log_neighbor_changes' in bgp_params %} +{% endif %} +{% if parameters.graceful_restart is defined %} + bgp graceful-restart {{ 'stalepath-time ' + parameters.graceful_restart.stalepath_time if parameters.graceful_restart.stalepath_time is defined }} +{% endif %} +{% if parameters.log_neighbor_changes is defined %} bgp log-neighbor-changes -{%- endif %} - -{%- if 'network_import_check' in bgp_params %} - bgp network import-check -{%- endif %} - -{%- if 'no_client_to_client_reflection' in bgp_params %} +{% endif %} +{% if parameters.network_import_check is defined %} + bgp network import-check +{% endif %} +{% if parameters.no_client_to_client_reflection is defined %} no bgp client-to-client reflection -{%- endif %} - -{%- if 'no_fast_external_failover' in bgp_params %} +{% endif %} +{% if parameters.no_fast_external_failover is defined %} no bgp fast-external-failover -{%- endif %} - -{%- if 'router_id' in bgp_params %} - bgp router-id {{ bgp_params.router_id }} -{%- endif %} - -{#- END parameters; set protocols bgp xxx parameters #} - -{%- if 'timers' in conf_bgp[asn] %} -{%- if ( ('holdtime' in conf_bgp[asn].timers) and ('keepalive' in conf_bgp[asn].timers ) ) %} - timers bgp {{conf_bgp[asn].timers.keepalive}} {{conf_bgp[asn].timers.holdtime}} -{%- endif %} -{%- endif %} - -{%- if 'route_map' in conf_bgp[asn] %} -! -ip protocol bgp route-map {{conf_bgp[asn].route_map}} -{%- endif %} -! -{%- endfor -%} -{#- END asn; router bgp xxx #} +{% endif %} +{% if parameters.router_id is defined and parameters.router_id is not none %} + bgp router-id {{ parameters.router_id }} +{% endif %} +{% endif %} +{% if timers is defined and timers.keepalive is defined and timers.holdtime is defined %} + timers bgp {{ timers.keepalive }} {{ timers.holdtime }} +{% endif %} + ! +{% if route_map is defined and route_map is not none %} + ip protocol bgp route-map {{ route_map }} +{% endif %} + ! diff --git a/data/templates/frr/igmp.frr.tmpl b/data/templates/frr/igmp.frr.tmpl index de4696c1f..cdb7ee6cc 100644 --- a/data/templates/frr/igmp.frr.tmpl +++ b/data/templates/frr/igmp.frr.tmpl @@ -1,41 +1,41 @@ ! -{% for iface in old_ifaces -%} +{% for iface in old_ifaces %} interface {{ iface }} -{% for group in old_ifaces[iface].gr_join -%} -{% if old_ifaces[iface].gr_join[group] -%} -{% for source in old_ifaces[iface].gr_join[group] -%} +{% for group in old_ifaces[iface].gr_join %} +{% if old_ifaces[iface].gr_join[group] %} +{% for source in old_ifaces[iface].gr_join[group] %} no ip igmp join {{ group }} {{ source }} -{% endfor -%} -{% else -%} +{% endfor %} +{% else %} no ip igmp join {{ group }} -{% endif -%} -{% endfor -%} +{% endif %} +{% endfor %} no ip igmp ! -{% endfor -%} -{% for iface in ifaces -%} +{% endfor %} +{% for iface in ifaces %} interface {{ iface }} -{% if ifaces[iface].version -%} +{% if ifaces[iface].version %} ip igmp version {{ ifaces[iface].version }} -{% else -%} +{% else %} {# IGMP default version 3 #} ip igmp -{% endif -%} -{% if ifaces[iface].query_interval -%} +{% endif %} +{% if ifaces[iface].query_interval %} ip igmp query-interval {{ ifaces[iface].query_interval }} -{% endif -%} -{% if ifaces[iface].query_max_resp_time -%} +{% endif %} +{% if ifaces[iface].query_max_resp_time %} ip igmp query-max-response-time {{ ifaces[iface].query_max_resp_time }} -{% endif -%} -{% for group in ifaces[iface].gr_join -%} -{% if ifaces[iface].gr_join[group] -%} -{% for source in ifaces[iface].gr_join[group] -%} +{% endif %} +{% for group in ifaces[iface].gr_join %} +{% if ifaces[iface].gr_join[group] %} +{% for source in ifaces[iface].gr_join[group] %} ip igmp join {{ group }} {{ source }} -{% endfor -%} -{% else -%} +{% endfor %} +{% else %} ip igmp join {{ group }} -{% endif -%} -{% endfor -%} +{% endif %} +{% endfor %} ! -{% endfor -%} +{% endfor %} ! diff --git a/data/templates/frr/ldpd.frr.tmpl b/data/templates/frr/ldpd.frr.tmpl index 4b7e5c5ea..280df41eb 100644 --- a/data/templates/frr/ldpd.frr.tmpl +++ b/data/templates/frr/ldpd.frr.tmpl @@ -1,187 +1,120 @@ ! -{% if mpls_ldp -%} +{% if ldp is defined %} mpls ldp -{% if old_router_id -%} -no router-id {{ old_router_id }} -{% endif -%} -{% if router_id -%} -router-id {{ router_id }} -{% endif -%} -{% if old_ldp.cisco_interop_tlv -%} -no dual-stack cisco-interop -{% endif -%} -{% if ldp.cisco_interop_tlv -%} +{% if ldp.router_id is defined %} +router-id {{ ldp.router_id }} +{% endif %} +{% if ldp.parameters is defined %} +{% if ldp.parameters.cisco_interop_tlv is defined %} dual-stack cisco-interop -{% endif -%} -{% if old_ldp.transport_prefer_ipv4 -%} -no dual-stack transport-connection prefer ipv4 -{% endif -%} -{% if ldp.transport_prefer_ipv4 -%} +{% endif %} +{% if ldp.parameters.transport_prefer_ipv4 is defined%} dual-stack transport-connection prefer ipv4 -{% endif -%} -{% for neighbor_id in old_ldp.neighbors -%} -no neighbor {{neighbor_id}} password {{old_ldp.neighbors[neighbor_id].password}} -{% if 'ttl_security' is defined -%} -{% if 'disable' in old_ldp.neighbors[neighbor_id].ttl_security %} -no neighbor {{neighbor_id}} ttl-security disable -{% else -%} -no neighbor {{neighbor_id}} ttl-security hops {{old_ldp.neighbors[neighbor_id].ttl_security}} -{% endif -%} -{% endif -%} -{% if 'session_holdtime' is defined -%} -no neighbor {{neighbor_id}} session holdtime {{old_ldp.neighbors[neighbor_id].session_holdtime}} -{% endif -%} -{% endfor -%} -{% for neighbor_id in ldp.neighbors -%} -neighbor {{neighbor_id}} password {{ldp.neighbors[neighbor_id].password}} -{% if 'ttl_security' is defined -%} -{% if 'disable' in ldp.neighbors[neighbor_id].ttl_security %} -neighbor {{neighbor_id}} ttl-security disable -{% else -%} -neighbor {{neighbor_id}} ttl-security hops {{ldp.neighbors[neighbor_id].ttl_security}} -{% endif -%} -{% endif -%} -{% if 'session_holdtime' is defined -%} -neighbor {{neighbor_id}} session holdtime {{ldp.neighbors[neighbor_id].session_holdtime}} -{% endif -%} -{% endfor -%} +{% endif %} +{% endif %} +{% if ldp.neighbor is defined %} +{% for neighbors in ldp.neighbor %} +{% if ldp.neighbor[neighbors].password is defined %} +neighbor {{neighbors}} password {{ldp.neighbor[neighbors].password}} +{% endif %} +{% if ldp.neighbor[neighbors].ttl_security is defined %} +{% if 'disable' in ldp.neighbor[neighbors].ttl_security %} +neighbor {{neighbors}} ttl-security disable +{% else %} +neighbor {{neighbors}} ttl-security hops {{ldp.neighbor[neighbors].ttl_security}} +{% endif %} +{% endif %} +{% if ldp.neighbor[neighbors].session_holdtime is defined %} +neighbor {{neighbors}} session holdtime {{ldp.neighbor[neighbors].session_holdtime}} +{% endif %} +{% endfor %} +{% endif %} ! +{% if ldp.discovery is defined %} +{% if ldp.discovery.transport_ipv4_address is defined %} address-family ipv4 label local allocate host-routes -{% if old_ldp.export_ipv4_exp -%} -no label local advertise explicit-null -{% endif -%} -{% if ldp.export_ipv4_exp -%} +{% if ldp.discovery.transport_ipv4_address is defined %} +discovery transport-address {{ ldp.discovery.transport_ipv4_address }} +{% endif %} +{% if ldp.discovery.hello_ipv4_holdtime is defined %} +discovery hello holdtime {{ ldp.discovery.hello_ipv4_holdtime }} +{% endif %} +{% if ldp.discovery.hello_ipv4_interval is defined %} +discovery hello interval {{ ldp.discovery.hello_ipv4_interval }} +{% endif %} +{% if ldp.discovery.session_ipv4_holdtime is defined %} +session holdtime {{ ldp.discovery.session_ipv4_holdtime }} +{% endif %} +{% if ldp.export is defined %} +{% if ldp.export.ipv4.explicit_null is defined %} label local advertise explicit-null -{% endif -%} -{% if old_ldp.d_transp_ipv4 -%} -no discovery transport-address {{ old_ldp.d_transp_ipv4 }} -{% endif -%} -{% if ldp.d_transp_ipv4 -%} -discovery transport-address {{ ldp.d_transp_ipv4 }} -{% endif -%} -{% if old_ldp.hello_ipv4_holdtime -%} -no discovery hello holdtime {{ old_ldp.hello_ipv4_holdtime }} -{% endif -%} -{% if ldp.hello_ipv4_holdtime -%} -discovery hello holdtime {{ ldp.hello_ipv4_holdtime }} -{% endif -%} -{% if old_ldp.hello_ipv4_interval -%} -no discovery hello interval {{ old_ldp.hello_ipv4_interval }} -{% endif -%} -{% if ldp.hello_ipv4_interval -%} -discovery hello interval {{ ldp.hello_ipv4_interval }} -{% endif -%} -{% if old_ldp.ses_ipv4_hold -%} -no session holdtime {{ old_ldp.ses_ipv4_hold }} -{% endif -%} -{% if ldp.ses_ipv4_hold -%} -session holdtime {{ ldp.ses_ipv4_hold }} -{% endif -%} -{% if old_ldp.target_ipv4_enable -%} -no discovery targeted-hello accept -{% endif -%} -{% if ldp.target_ipv4_enable -%} +{% endif %} +{% endif %} +{% if ldp.targeted_neighbor is defined %} +{% if ldp.targeted_neighbor.ipv4.enable is defined %} discovery targeted-hello accept -{% endif -%} -{% if old_ldp.target_ipv4_hello_int -%} -no discovery targeted-hello interval {{ old_ldp.target_ipv4_hello_int }} -{% endif -%} -{% if ldp.target_ipv4_hello_int -%} -discovery targeted-hello interval {{ ldp.target_ipv4_hello_int }} -{% endif -%} -{% if old_ldp.target_ipv4_hello_hold -%} -no discovery targeted-hello holdtime {{ old_ldp.target_ipv4_hello_hold }} -{% endif -%} -{% if ldp.target_ipv4_hello_hold -%} -discovery targeted-hello holdtime {{ ldp.target_ipv4_hello_hold }} -{% endif -%} -{% for address in old_ldp.target_ipv4_addresses -%} -no neighbor {{address}} targeted -{% endfor -%} -{% for address in ldp.target_ipv4_addresses -%} -neighbor {{address}} targeted -{% endfor -%} -{% for interface in old_ldp.interfaces -%} -no interface {{interface}} -{% endfor -%} -{% for interface in ldp.interfaces -%} -interface {{interface}} -{% endfor -%} -! -! +{% endif %} +{% if ldp.targeted_neighbor.ipv4.hello_holdtime is defined %} +discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv4.hello_holdtime }} +{% endif %} +{% if ldp.targeted_neighbor.ipv4.hello_interval is defined %} +discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv4.hello_interval }} +{% endif %} +{% for addresses in ldp.targeted_neighbor.ipv4.address %} +neighbor {{addresses}} targeted +{% endfor %} +{% endif %} +{% for interfaces in ldp.interface %} +interface {{interfaces}} +{% endfor %} exit-address-family +{% else %} +no address-family ipv4 +{% endif %} +{% endif %} ! -{% if ldp.d_transp_ipv6 -%} +{% if ldp.discovery is defined %} +{% if ldp.discovery.transport_ipv6_address is defined %} address-family ipv6 label local allocate host-routes -{% if old_ldp.export_ipv6_exp -%} -no label local advertise explicit-null -{% endif -%} -{% if ldp.export_ipv6_exp -%} +{% if ldp.discovery.transport_ipv6_address is defined %} +discovery transport-address {{ ldp.discovery.transport_ipv6_address }} +{% endif %} +{% if ldp.discovery.hello_ipv6_holdtime is defined %} +discovery hello holdtime {{ ldp.discovery.hello_ipv6_holdtime }} +{% endif %} +{% if ldp.discovery.hello_ipv6_interval is defined %} +discovery hello interval {{ ldp.discovery.hello_ipv6_interval }} +{% endif %} +{% if ldp.discovery.session_ipv6_holdtime is defined %} +session holdtime {{ ldp.discovery.session_ipv6_holdtime }} +{% endif %} +{% if ldp.export is defined %} +{% if ldp.export.ipv6.explicit_null is defined %} label local advertise explicit-null -{% endif -%} -{% if old_ldp.ses_ipv6_hold -%} -no session holdtime {{ old_ldp.ses_ipv6_hold }} -{% endif -%} -{% if ldp.ses_ipv6_hold -%} -session holdtime {{ ldp.ses_ipv6_hold }} -{% endif -%} -{% if old_ldp.d_transp_ipv6 -%} -no discovery transport-address {{ old_ldp.d_transp_ipv6 }} -{% endif -%} -{% if ldp.d_transp_ipv6 -%} -discovery transport-address {{ ldp.d_transp_ipv6 }} -{% endif -%} -{% if old_ldp.hello_ipv6_holdtime -%} -no discovery hello holdtime {{ old_ldp.hello_ipv6_holdtime }} -{% endif -%} -{% if ldp.hello_ipv6_holdtime -%} -discovery hello holdtime {{ ldp.hello_ipv6_holdtime }} -{% endif -%} -{% if old_ldp.hello_ipv6_interval -%} -no discovery hello interval {{ old_ldp.hello_ipv6_interval }} -{% endif -%} -{% if ldp.hello_ipv6_interval -%} -discovery hello interval {{ ldp.hello_ipv6_interval }} -{% endif -%} -{% if old_ldp.target_ipv6_enable -%} -no discovery targeted-hello accept -{% endif -%} -{% if ldp.target_ipv6_enable -%} +{% endif %} +{% endif %} +{% if ldp.targeted_neighbor is defined %} +{% if ldp.targeted_neighbor.ipv6.enable is defined %} discovery targeted-hello accept -{% endif -%} -{% if old_ldp.target_ipv6_hello_int -%} -no discovery targeted-hello interval {{ old_ldp.target_ipv6_hello_int }} -{% endif -%} -{% if ldp.target_ipv6_hello_int -%} -discovery targeted-hello interval {{ ldp.target_ipv6_hello_int }} -{% endif -%} -{% if old_ldp.target_ipv6_hello_hold -%} -no discovery targeted-hello holdtime {{ old_ldp.target_ipv6_hello_hold }} -{% endif -%} -{% if ldp.target_ipv6_hello_hold -%} -discovery targeted-hello holdtime {{ ldp.target_ipv6_hello_hold }} -{% endif -%} -{% for address in old_ldp.target_ipv6_addresses -%} -no neighbor {{address}} targeted -{% endfor -%} -{% for address in ldp.target_ipv6_addresses -%} -neighbor {{address}} targeted -{% endfor -%} -{% for interface in old_ldp.interfaces -%} -no interface {{interface}} -{% endfor -%} -{% for interface in ldp.interfaces -%} -interface {{interface}} -{% endfor -%} -! +{% endif %} +{% if ldp.targeted_neighbor.ipv6.hello_holdtime is defined %} +discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv6.hello_holdtime }} +{% endif %} +{% if ldp.targeted_neighbor.ipv6.hello_interval is defined %} +discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv6.hello_interval }} +{% endif %} +{% for addresses in ldp.targeted_neighbor.ipv6.address %} +neighbor {{addresses}} targeted +{% endfor %} +{% endif %} +{% for interfaces in ldp.interface %} +interface {{interfaces}} +{% endfor %} exit-address-family -{% else -%} +{% else %} no address-family ipv6 -{% endif -%} -! -{% else -%} -no mpls ldp -{% endif -%} -!
\ No newline at end of file +{% endif %} +{% endif %} +{% endif %} diff --git a/data/templates/frr/pimd.frr.tmpl b/data/templates/frr/pimd.frr.tmpl index 1d1532c60..a5b56223a 100644 --- a/data/templates/frr/pimd.frr.tmpl +++ b/data/templates/frr/pimd.frr.tmpl @@ -1,34 +1,34 @@ ! -{% for rp_addr in old_pim.rp -%} -{% for group in old_pim.rp[rp_addr] -%} +{% for rp_addr in old_pim.rp %} +{% for group in old_pim.rp[rp_addr] %} no ip pim rp {{ rp_addr }} {{ group }} -{% endfor -%} -{% endfor -%} -{% if old_pim.rp_keep_alive -%} +{% endfor %} +{% endfor %} +{% if old_pim.rp_keep_alive %} no ip pim rp keep-alive-timer {{ old_pim.rp_keep_alive }} -{% endif -%} -{% for iface in old_pim.ifaces -%} +{% endif %} +{% for iface in old_pim.ifaces %} interface {{ iface }} no ip pim ! -{% endfor -%} -{% for iface in pim.ifaces -%} +{% endfor %} +{% for iface in pim.ifaces %} interface {{ iface }} ip pim -{% if pim.ifaces[iface].dr_prio -%} +{% if pim.ifaces[iface].dr_prio %} ip pim drpriority {{ pim.ifaces[iface].dr_prio }} -{% endif -%} -{% if pim.ifaces[iface].hello -%} +{% endif %} +{% if pim.ifaces[iface].hello %} ip pim hello {{ pim.ifaces[iface].hello }} -{% endif -%} +{% endif %} ! -{% endfor -%} -{% for rp_addr in pim.rp -%} -{% for group in pim.rp[rp_addr] -%} +{% endfor %} +{% for rp_addr in pim.rp %} +{% for group in pim.rp[rp_addr] %} ip pim rp {{ rp_addr }} {{ group }} -{% endfor -%} -{% endfor -%} -{% if pim.rp_keep_alive -%} +{% endfor %} +{% endfor %} +{% if pim.rp_keep_alive %} ip pim rp keep-alive-timer {{ pim.rp_keep_alive }} -{% endif -%} +{% endif %} ! diff --git a/data/templates/frr/rip.frr.tmpl b/data/templates/frr/rip.frr.tmpl index 60bc686bd..83df4e203 100644 --- a/data/templates/frr/rip.frr.tmpl +++ b/data/templates/frr/rip.frr.tmpl @@ -1,143 +1,143 @@ ! -{% if rip_conf -%} +{% if rip_conf %} router rip -{% if old_default_distance -%} +{% if old_default_distance %} no distance {{old_default_distance}} -{% endif -%} -{% if default_distance -%} +{% endif %} +{% if default_distance %} distance {{default_distance}} -{% endif -%} -{% if old_default_originate -%} +{% endif %} +{% if old_default_originate %} no default-information originate -{% endif -%} -{% if default_originate -%} +{% endif %} +{% if default_originate %} default-information originate -{% endif -%} -{% if old_rip.default_metric -%} +{% endif %} +{% if old_rip.default_metric %} no default-metric {{old_rip.default_metric}} -{% endif -%} -{% if rip.default_metric -%} +{% endif %} +{% if rip.default_metric %} default-metric {{rip.default_metric}} -{% endif -%} -{% for protocol in old_rip.redist -%} -{% if old_rip.redist[protocol]['metric'] and old_rip.redist[protocol]['route_map'] -%} +{% endif %} +{% for protocol in old_rip.redist %} +{% if old_rip.redist[protocol]['metric'] and old_rip.redist[protocol]['route_map'] %} no redistribute {{protocol}} metric {{rip.redist[protocol]['metric']}} route-map {{rip.redist[protocol]['route_map']}} -{% elif old_rip.redist[protocol]['metric'] -%} +{% elif old_rip.redist[protocol]['metric'] %} no redistribute {{protocol}} metric {{old_rip.redist[protocol]['metric']}} -{% elif old_rip.redist[protocol]['route_map'] -%} +{% elif old_rip.redist[protocol]['route_map'] %} no redistribute {{protocol}} route-map {{old_rip.redist[protocol]['route_map']}} -{% else -%} +{% else %} no redistribute {{protocol}} -{% endif -%} -{% endfor -%} -{% for protocol in rip.redist -%} -{% if rip.redist[protocol]['metric'] and rip.redist[protocol]['route_map'] -%} +{% endif %} +{% endfor %} +{% for protocol in rip.redist %} +{% if rip.redist[protocol]['metric'] and rip.redist[protocol]['route_map'] %} redistribute {{protocol}} metric {{rip.redist[protocol]['metric']}} route-map {{rip.redist[protocol]['route_map']}} -{% elif rip.redist[protocol]['metric'] -%} +{% elif rip.redist[protocol]['metric'] %} redistribute {{protocol}} metric {{rip.redist[protocol]['metric']}} -{% elif rip.redist[protocol]['route_map'] -%} +{% elif rip.redist[protocol]['route_map'] %} redistribute {{protocol}} route-map {{rip.redist[protocol]['route_map']}} -{% else -%} +{% else %} redistribute {{protocol}} -{% endif -%} -{% endfor -%} -{% for iface in old_rip.distribute -%} -{% if old_rip.distribute[iface].iface_access_list_in -%} +{% endif %} +{% endfor %} +{% for iface in old_rip.distribute %} +{% if old_rip.distribute[iface].iface_access_list_in %} no distribute-list {{old_rip.distribute[iface].iface_access_list_in}} in {{iface}} -{% endif -%} -{% if old_rip.distribute[iface].iface_access_list_out -%} +{% endif %} +{% if old_rip.distribute[iface].iface_access_list_out %} no distribute-list {{old_rip.distribute[iface].iface_access_list_out}} out {{iface}} -{% endif -%} -{% if old_rip.distribute[iface].iface_prefix_list_in -%} +{% endif %} +{% if old_rip.distribute[iface].iface_prefix_list_in %} no distribute-list prefix {{old_rip.distribute[iface].iface_prefix_list_in}} in {{iface}} -{% endif -%} -{% if old_rip.distribute[iface].iface_prefix_list_out -%} +{% endif %} +{% if old_rip.distribute[iface].iface_prefix_list_out %} no distribute-list prefix {{old_rip.distribute[iface].iface_prefix_list_out}} out {{iface}} -{% endif -%} -{% endfor -%} -{% for iface in rip.distribute -%} -{% if rip.distribute[iface].iface_access_list_in -%} +{% endif %} +{% endfor %} +{% for iface in rip.distribute %} +{% if rip.distribute[iface].iface_access_list_in %} distribute-list {{rip.distribute[iface].iface_access_list_in}} in {{iface}} -{% endif -%} -{% if rip.distribute[iface].iface_access_list_out -%} +{% endif %} +{% if rip.distribute[iface].iface_access_list_out %} distribute-list {{rip.distribute[iface].iface_access_list_out}} out {{iface}} -{% endif -%} -{% if rip.distribute[iface].iface_prefix_list_in -%} +{% endif %} +{% if rip.distribute[iface].iface_prefix_list_in %} distribute-list prefix {{rip.distribute[iface].iface_prefix_list_in}} in {{iface}} -{% endif -%} -{% if rip.distribute[iface].iface_prefix_list_out -%} +{% endif %} +{% if rip.distribute[iface].iface_prefix_list_out %} distribute-list prefix {{rip.distribute[iface].iface_prefix_list_out}} out {{iface}} -{% endif -%} -{% endfor -%} -{% if old_rip.dist_acl_in -%} +{% endif %} +{% endfor %} +{% if old_rip.dist_acl_in %} no distribute-list {{old_rip.dist_acl_in}} in -{% endif -%} -{% if rip.dist_acl_in -%} +{% endif %} +{% if rip.dist_acl_in %} distribute-list {{rip.dist_acl_in}} in -{% endif -%} -{% if old_rip.dist_acl_out -%} +{% endif %} +{% if old_rip.dist_acl_out %} no distribute-list {{old_rip.dist_acl_out}} out -{% endif -%} -{% if rip.dist_acl_out -%} +{% endif %} +{% if rip.dist_acl_out %} distribute-list {{rip.dist_acl_out}} out -{% endif -%} -{% if old_rip.dist_prfx_in -%} +{% endif %} +{% if old_rip.dist_prfx_in %} no distribute-list prefix {{old_rip.dist_prfx_in}} in -{% endif -%} -{% if rip.dist_prfx_in -%} +{% endif %} +{% if rip.dist_prfx_in %} distribute-list prefix {{rip.dist_prfx_in}} in -{% endif -%} -{% if old_rip.dist_prfx_out -%} +{% endif %} +{% if old_rip.dist_prfx_out %} no distribute-list prefix {{old_rip.dist_prfx_out}} out -{% endif -%} -{% if rip.dist_prfx_out -%} +{% endif %} +{% if rip.dist_prfx_out %} distribute-list prefix {{rip.dist_prfx_out}} out -{% endif -%} -{% for network in old_rip.networks -%} +{% endif %} +{% for network in old_rip.networks %} no network {{network}} -{% endfor -%} -{% for network in rip.networks -%} +{% endfor %} +{% for network in rip.networks %} network {{network}} -{% endfor -%} -{% for iface in old_rip.ifaces -%} +{% endfor %} +{% for iface in old_rip.ifaces %} no network {{iface}} -{% endfor -%} -{% for iface in rip.ifaces -%} +{% endfor %} +{% for iface in rip.ifaces %} network {{iface}} -{% endfor -%} -{% for neighbor in old_rip.neighbors -%} +{% endfor %} +{% for neighbor in old_rip.neighbors %} no neighbor {{neighbor}} -{% endfor -%} -{% for neighbor in rip.neighbors -%} +{% endfor %} +{% for neighbor in rip.neighbors %} neighbor {{neighbor}} -{% endfor -%} -{% for net in rip.net_distance -%} -{% if rip.net_distance[net].access_list and rip.net_distance[net].distance -%} +{% endfor %} +{% for net in rip.net_distance %} +{% if rip.net_distance[net].access_list and rip.net_distance[net].distance %} distance {{rip.net_distance[net].distance}} {{net}} {{rip.net_distance[net].access_list}} -{% else -%} +{% else %} distance {{rip.net_distance[net].distance}} {{net}} -{% endif -%} -{% endfor -%} -{% for passive_iface in old_rip.passive_iface -%} +{% endif %} +{% endfor %} +{% for passive_iface in old_rip.passive_iface %} no passive-interface {{passive_iface}} -{% endfor -%} -{% for passive_iface in rip.passive_iface -%} +{% endfor %} +{% for passive_iface in rip.passive_iface %} passive-interface {{passive_iface}} -{% endfor -%} -{% for route in old_rip.route -%} +{% endfor %} +{% for route in old_rip.route %} no route {{route}} -{% endfor -%} -{% for route in rip.route -%} +{% endfor %} +{% for route in rip.route %} route {{route}} -{% endfor -%} -{% if old_rip.timer_update or old_rip.timer_timeout or old_rip.timer_garbage -%} +{% endfor %} +{% if old_rip.timer_update or old_rip.timer_timeout or old_rip.timer_garbage %} no timers basic -{% endif -%} -{% if rip.timer_update or rip.timer_timeout or rip.timer_garbage -%} +{% endif %} +{% if rip.timer_update or rip.timer_timeout or rip.timer_garbage %} timers basic {{rip.timer_update}} {{rip.timer_timeout}} {{rip.timer_garbage}} -{% endif -%} +{% endif %} ! -{% else -%} +{% else %} no router rip ! -{% endif -%} +{% endif %} diff --git a/data/templates/frr/static_mcast.frr.tmpl b/data/templates/frr/static_mcast.frr.tmpl index 86d619ab0..38635af32 100644 --- a/data/templates/frr/static_mcast.frr.tmpl +++ b/data/templates/frr/static_mcast.frr.tmpl @@ -1,20 +1,20 @@ ! -{% for route_gr in old_mroute -%} -{% for nh in old_mroute[route_gr] -%} -{% if old_mroute[route_gr][nh] -%} +{% for route_gr in old_mroute %} +{% for nh in old_mroute[route_gr] %} +{% if old_mroute[route_gr][nh] %} no ip mroute {{ route_gr }} {{ nh }} {{ old_mroute[route_gr][nh] }} -{% else -%} +{% else %} no ip mroute {{ route_gr }} {{ nh }} -{% endif -%} -{% endfor -%} -{% endfor -%} -{% for route_gr in mroute -%} -{% for nh in mroute[route_gr] -%} -{% if mroute[route_gr][nh] -%} +{% endif %} +{% endfor %} +{% endfor %} +{% for route_gr in mroute %} +{% for nh in mroute[route_gr] %} +{% if mroute[route_gr][nh] %} ip mroute {{ route_gr }} {{ nh }} {{ mroute[route_gr][nh] }} -{% else -%} +{% else %} ip mroute {{ route_gr }} {{ nh }} -{% endif -%} -{% endfor -%} -{% endfor -%} +{% endif %} +{% endfor %} +{% endfor %} ! diff --git a/data/templates/ids/fastnetmon.tmpl b/data/templates/ids/fastnetmon.tmpl index 71a1b2bd7..1f6a1c808 100644 --- a/data/templates/ids/fastnetmon.tmpl +++ b/data/templates/ids/fastnetmon.tmpl @@ -27,34 +27,34 @@ enable_subnet_counters = off {% if "mirror" in mode %} mirror_afpacket = on -{% endif -%} +{% endif %} {% if "in" in direction %} process_incoming_traffic = on -{% endif -%} +{% endif %} {% if "out" in direction %} process_outgoing_traffic = on -{% endif -%} +{% endif %} {% for th in threshold %} {% if th == "fps" %} ban_for_flows = on threshold_flows = {{ threshold[th] }} -{% endif -%} +{% endif %} {% if th == "mbps" %} ban_for_bandwidth = on threshold_mbps = {{ threshold[th] }} -{% endif -%} +{% endif %} {% if th == "pps" %} ban_for_pps = on threshold_pps = {{ threshold[th] }} -{% endif -%} -{% endfor -%} +{% endif %} +{% endfor %} {% if listen_interface %} {% set value = listen_interface if listen_interface is string else listen_interface | join(',') %} interfaces = {{ value }} -{% endif -%} +{% endif %} {% if alert_script %} notify_script_path = {{ alert_script }} -{% endif -%} +{% endif %} diff --git a/data/templates/igmp-proxy/igmpproxy.conf.tmpl b/data/templates/igmp-proxy/igmpproxy.conf.tmpl index c7fc5cef5..e3966def3 100644 --- a/data/templates/igmp-proxy/igmpproxy.conf.tmpl +++ b/data/templates/igmp-proxy/igmpproxy.conf.tmpl @@ -2,36 +2,39 @@ # # autogenerated by igmp_proxy.py # -# The configuration file must define one upstream -# interface, and one or more downstream interfaces. +# The configuration file must define one upstream interface, and one or more +# downstream interfaces. # -# If multicast traffic originates outside the -# upstream subnet, the "altnet" option can be -# used in order to define legal multicast sources. -# (Se example...) +# If multicast traffic originates outside the upstream subnet, the "altnet" +# option can be used in order to define legal multicast sources. # -# The "quickleave" should be used to avoid saturation -# of the upstream link. The option should only -# be used if it's absolutely nessecary to -# accurately imitate just one Client. +# The "quickleave" should be used to avoid saturation of the upstream link. The +# option should only be used if it's absolutely nessecary to accurately imitate +# just one Client. # ######################################################## -{% if not disable_quickleave -%} +{% if disable_quickleave is not defined %} quickleave -{% endif -%} +{% endif %} +{% if interface is defined and interface is not none %} +{% for iface, config in interface.items() %} -{% for interface in interfaces %} -# Configuration for {{ interface.name }} ({{ interface.role }} interface) -{% if interface.role == 'disabled' -%} -phyint {{ interface.name }} disabled -{%- else -%} -phyint {{ interface.name }} {{ interface.role }} ratelimit 0 threshold {{ interface.threshold }} -{%- endif -%} -{%- for subnet in interface.alt_subnet %} +# Configuration for {{ iface }} ({{ config.role }} interface) +{% if config.role == 'disabled' %} +phyint {{ iface }} disabled +{% else %} +phyint {{ iface }} {{ config.role }} ratelimit 0 threshold {{ config.threshold }} +{% endif %} +{% if config.alt_subnet is defined and config.alt_subnet is not none %} +{% for subnet in config.alt_subnet %} altnet {{ subnet }} -{%- endfor %} -{%- for subnet in interface.whitelist %} +{% endfor %} +{% endif %} +{% if config.whitelist is defined and config.whitelist is not none %} +{% for subnet in config.whitelist %} whitelist {{ subnet }} -{%- endfor %} -{% endfor %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} diff --git a/data/templates/lldp/vyos.conf.tmpl b/data/templates/lldp/vyos.conf.tmpl index e724f42c6..07bbaf604 100644 --- a/data/templates/lldp/vyos.conf.tmpl +++ b/data/templates/lldp/vyos.conf.tmpl @@ -2,19 +2,19 @@ configure system platform VyOS configure system description "VyOS {{ options.description }}" -{% if options.listen_on -%} +{% if options.listen_on %} configure system interface pattern "{{ ( options.listen_on | select('equalto','all') | map('replace','all','*') | list + options.listen_on | select('equalto','!all') | map('replace','!all','!*') | list + options.listen_on | reject('equalto','all') | reject('equalto','!all') | list ) | unique | join(",") }}" -{%- endif %} -{% if options.mgmt_addr -%} +{% endif %} +{% if options.mgmt_addr %} configure system ip management pattern {{ options.mgmt_addr | join(",") }} -{%- endif %} -{%- for loc in location -%} -{%- if loc.elin %} +{% endif %} +{% for loc in location %} +{% if loc.elin %} configure ports {{ loc.name }} med location elin "{{ loc.elin }}" -{%- endif %} -{%- if loc.coordinate_based %} +{% endif %} +{% if loc.coordinate_based %} configure ports {{ loc.name }} med location coordinate {% if loc.coordinate_based.latitude %}latitude {{ loc.coordinate_based.latitude }}{% endif %} {% if loc.coordinate_based.longitude %}longitude {{ loc.coordinate_based.longitude }}{% endif %} {% if loc.coordinate_based.altitude %}altitude {{ loc.coordinate_based.altitude }} m{% endif %} {% if loc.coordinate_based.datum %}datum {{ loc.coordinate_based.datum }}{% endif %} -{%- endif %} +{% endif %} {% endfor %} diff --git a/data/templates/netflow/uacctd.conf.tmpl b/data/templates/netflow/uacctd.conf.tmpl index d8615566f..fdf96e7c3 100644 --- a/data/templates/netflow/uacctd.conf.tmpl +++ b/data/templates/netflow/uacctd.conf.tmpl @@ -8,62 +8,62 @@ snaplen: {{ snaplen }} aggregate: in_iface,src_mac,dst_mac,vlan,src_host,dst_host,src_port,dst_port,proto,tos,flows plugin_pipe_size: {{ templatecfg['plugin_pipe_size'] }} plugin_buffer_size: {{ templatecfg['plugin_buffer_size'] }} -{%- if templatecfg['syslog-facility'] != none %} +{% if templatecfg['syslog-facility'] != none %} syslog: {{ templatecfg['syslog-facility'] }} -{%- endif %} -{%- if templatecfg['disable-imt'] == none %} +{% endif %} +{% if templatecfg['disable-imt'] == none %} imt_path: /tmp/uacctd.pipe imt_mem_pools_number: 169 -{%- endif %} +{% endif %} plugins: -{%- if templatecfg['netflow']['servers'] != none -%} +{% if templatecfg['netflow']['servers'] != none %} {% for server in templatecfg['netflow']['servers'] %} - {%- if loop.last -%}nfprobe[nf_{{ server['address'] }}]{%- else %}nfprobe[nf_{{ server['address'] }}],{%- endif %} - {%- endfor -%} + {% if loop.last %}nfprobe[nf_{{ server['address'] }}]{% else %}nfprobe[nf_{{ server['address'] }}],{% endif %} + {% endfor %} {% set plugins_presented = true %} -{%- endif %} -{%- if templatecfg['sflow']['servers'] != none -%} - {% if plugins_presented -%} - {%- for server in templatecfg['sflow']['servers'] -%} +{% endif %} +{% if templatecfg['sflow']['servers'] != none %} + {% if plugins_presented %} + {% for server in templatecfg['sflow']['servers'] %} ,sfprobe[sf_{{ server['address'] }}] - {%- endfor %} - {%- else %} - {%- for server in templatecfg['sflow']['servers'] %} - {%- if loop.last -%}sfprobe[sf_{{ server['address'] }}]{%- else %}sfprobe[sf_{{ server['address'] }}],{%- endif %} - {%- endfor %} - {%- endif -%} + {% endfor %} + {% else %} + {% for server in templatecfg['sflow']['servers'] %} + {% if loop.last %}sfprobe[sf_{{ server['address'] }}]{% else %}sfprobe[sf_{{ server['address'] }}],{% endif %} + {% endfor %} + {% endif %} {% set plugins_presented = true %} -{%- endif %} -{%- if templatecfg['disable-imt'] == none %} - {%- if plugins_presented -%},memory{%- else %}memory{%- endif %} -{%- endif %} -{%- if templatecfg['netflow']['servers'] != none %} -{%- for server in templatecfg['netflow']['servers'] %} +{% endif %} +{% if templatecfg['disable-imt'] == none %} + {% if plugins_presented %},memory{% else %}memory{% endif %} +{% endif %} +{% if templatecfg['netflow']['servers'] != none %} +{% for server in templatecfg['netflow']['servers'] %} nfprobe_receiver[nf_{{ server['address'] }}]: {{ server['address'] }}:{{ server['port'] }} nfprobe_version[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['version'] }} -{%- if templatecfg['netflow']['engine-id'] != none %} +{% if templatecfg['netflow']['engine-id'] != none %} nfprobe_engine[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['engine-id'] }} -{%- endif %} -{%- if templatecfg['netflow']['max-flows'] != none %} +{% endif %} +{% if templatecfg['netflow']['max-flows'] != none %} nfprobe_maxflows[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['max-flows'] }} -{%- endif %} -{%- if templatecfg['netflow']['sampling-rate'] != none %} +{% endif %} +{% if templatecfg['netflow']['sampling-rate'] != none %} sampling_rate[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['sampling-rate'] }} -{%- endif %} -{%- if templatecfg['netflow']['source-ip'] != none %} +{% endif %} +{% if templatecfg['netflow']['source-ip'] != none %} nfprobe_source_ip[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['source-ip'] }} -{%- endif %} -{%- if templatecfg['netflow']['timeout_string'] != '' %} +{% endif %} +{% if templatecfg['netflow']['timeout_string'] != '' %} nfprobe_timeouts[nf_{{ server['address'] }}]: {{ templatecfg['netflow']['timeout_string'] }} -{%- endif %} -{%- endfor %} -{%- endif %} -{%- if templatecfg['sflow']['servers'] != none %} -{%- for server in templatecfg['sflow']['servers'] %} +{% endif %} +{% endfor %} +{% endif %} +{% if templatecfg['sflow']['servers'] != none %} +{% for server in templatecfg['sflow']['servers'] %} sfprobe_receiver[sf_{{ server['address'] }}]: {{ server['address'] }}:{{ server['port'] }} sfprobe_agentip[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['agent-address'] }} -{%- if templatecfg['sflow']['sampling-rate'] != none %} +{% if templatecfg['sflow']['sampling-rate'] != none %} sampling_rate[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['sampling-rate'] }} -{%- endif %} -{%- endfor %} +{% endif %} +{% endfor %} {% endif %} diff --git a/data/templates/ntp/ntp.conf.tmpl b/data/templates/ntp/ntp.conf.tmpl index bb0067bfb..3f319c89b 100644 --- a/data/templates/ntp/ntp.conf.tmpl +++ b/data/templates/ntp/ntp.conf.tmpl @@ -13,13 +13,13 @@ restrict -6 ::1 # # Configurable section # -{% if server %} -{% for srv in server %} -{% set options = '' %} -{% set options = options + 'noselect ' if server[srv].noselect is defined else '' %} -{% set options = options + 'preempt ' if server[srv].preempt is defined else '' %} -{% set options = options + 'prefer ' if server[srv].prefer is defined else '' %} -server {{ srv | replace('_', '-') }} iburst {{ options }} +{% if server is defined and server is not none %} +{% for server, config in server.items() %} +{% set association = 'server' %} +{% if config.pool is defined %} +{% set association = 'pool' %} +{% endif %} +{{ association }} {{ server | replace('_', '-') }} iburst {{ 'noselect' if config.noselect is defined }} {{ 'preempt' if config.preempt is defined }} {{ 'prefer' if config.prefer is defined }} {% endfor %} {% endif %} diff --git a/data/templates/openvpn/auth.pw.tmpl b/data/templates/openvpn/auth.pw.tmpl new file mode 100644 index 000000000..9b20c9742 --- /dev/null +++ b/data/templates/openvpn/auth.pw.tmpl @@ -0,0 +1,5 @@ +{# Autogenerated by interfaces-openvpn.py #} +{% if authentication is defined and authentication is not none %} +{{ authentication.username }} +{{ authentication.password }} +{% endif %} diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index 1fdf6b848..a1daaa078 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -14,9 +14,9 @@ dev {{ ifname }} persist-key iproute /usr/libexec/vyos/system/unpriv-ip {% if protocol == 'tcp-active' %} -proto tcp6-client +proto tcp-client {% elif protocol == 'tcp-passive' %} -proto tcp6-server +proto tcp-server {% else %} proto udp {% endif %} @@ -52,13 +52,14 @@ push "redirect-gateway def1" compress lzo {% endif %} -{% if 'client' in mode %} +{% if mode == 'client' %} # # OpenVPN Client mode # client nobind -{% elif 'server' in mode %} + +{% elif mode == 'server' %} # # OpenVPN Server mode # @@ -94,7 +95,7 @@ max-clients {{ server.max_connections }} client-config-dir /run/openvpn/ccd/{{ ifname }} {% endif %} {% endif %} -keepalive {{ keep_alive.interval }} {{ keep_alive.failure_count }} +keepalive {{ keep_alive.interval }} {{ keep_alive.interval|int * keep_alive.failure_count|int }} management /run/openvpn/openvpn-mgmt-intf unix {% if server is defined and server is not none %} {% if server.reject_unconfigured_clients is defined %} @@ -129,6 +130,7 @@ push "route-ipv6 {{ route6 }}" push "dhcp-option DNS6 {{ ns6 }}" {% endfor %} {% endif %} + {% else %} # # OpenVPN site-2-site mode @@ -136,19 +138,24 @@ push "dhcp-option DNS6 {{ ns6 }}" ping {{ keep_alive.interval }} ping-restart {{ keep_alive.failure_count }} -{% for laddr, laddr_conf in local_address.items() if laddr | is_ipv4 %} -{% if laddr_conf is defined and laddr_conf.subnet_mask is defined and laddr_conf.subnet_mask is not none %} +{% if device_type == 'tap' %} +{% for laddr, laddr_conf in local_address.items() if laddr | is_ipv4 %} +{% if laddr_conf is defined and laddr_conf.subnet_mask is defined and laddr_conf.subnet_mask is not none %} ifconfig {{ laddr }} {{ laddr_conf.subnet_mask }} -{% else %} -{% for raddr in remote_address %} -{% if raddr | is_ipv4 %} +{% endif %} +{% endfor %} +{% else %} +{% for laddr in local_address if laddr | is_ipv4 %} +{% for raddr in remote_address if raddr | is_ipv4 %} ifconfig {{ laddr }} {{ raddr }} -{% else %} +{% endfor %} +{% endfor %} +{% for laddr in local_address if laddr | is_ipv6 %} +{% for raddr in remote_address if raddr | is_ipv6 %} ifconfig-ipv6 {{ laddr }} {{ raddr }} -{% endif %} {% endfor %} -{% endif %} -{% endfor %} +{% endfor %} +{% endif %} {% endif %} {% if tls is defined and tls is not none %} diff --git a/data/templates/pppoe/ip-pre-up.script.tmpl b/data/templates/pppoe/ip-pre-up.script.tmpl index cf85ed067..a54e4e9bd 100644 --- a/data/templates/pppoe/ip-pre-up.script.tmpl +++ b/data/templates/pppoe/ip-pre-up.script.tmpl @@ -12,7 +12,7 @@ logger -t pppd[$DIALER_PID] "executing $0" echo "{{ description }}" > /sys/class/net/{{ ifname }}/ifalias -{% if vrf -%} +{% if vrf %} logger -t pppd[$DIALER_PID] "configuring dialer interface $6 for VRF {{ vrf }}" ip link set dev {{ ifname }} master {{ vrf }} {% endif %} diff --git a/data/templates/pppoe/ip-up.script.tmpl b/data/templates/pppoe/ip-up.script.tmpl index 568e21c4e..302756960 100644 --- a/data/templates/pppoe/ip-up.script.tmpl +++ b/data/templates/pppoe/ip-up.script.tmpl @@ -11,7 +11,7 @@ fi DIALER_PID=$(cat /var/run/{{ ifname }}.pid) logger -t pppd[$DIALER_PID] "executing $0" -{% if default_route != 'none' -%} +{% if default_route != 'none' %} # See https://phabricator.vyos.net/T2248 & T2220. Determine if we are enslaved # to a VRF, this is needed to properly insert the default route. @@ -28,14 +28,14 @@ if [ -d /sys/class/net/{{ ifname }}/upper_* ]; then VRF_NAME="vrf ${VRF}" fi -{% if default_route == 'auto' -%} +{% if default_route == 'auto' %} # Only insert a new default route if there is no default route configured routes=$(vtysh -c "show running-config" | sed -n "/${SED_OPT}/,/!/p" | grep 0.0.0.0/0 | wc -l) if [ "$routes" -ne 0 ]; then exit 1 fi -{% elif default_route == 'force' -%} +{% elif default_route == 'force' %} # Retrieve current static default routes and remove it from the routing table vtysh -c "show running-config" | sed -n "/${SED_OPT}/,/!/p" | grep 0.0.0.0/0 | while read route ; do vtysh -c "conf t" ${VTY_OPT} -c "no ${route} ${VRF_NAME}" diff --git a/data/templates/pppoe/ipv6-up.script.tmpl b/data/templates/pppoe/ipv6-up.script.tmpl index d0a62478c..ed6102d6c 100644 --- a/data/templates/pppoe/ipv6-up.script.tmpl +++ b/data/templates/pppoe/ipv6-up.script.tmpl @@ -7,7 +7,7 @@ if [ "$6" != "{{ ifname }}" ]; then exit fi -{% if ipv6 is defined and ipv6.address is defined and ipv6.address.autoconf is defined -%} +{% if ipv6 is defined and ipv6.address is defined and ipv6.address.autoconf is defined %} # add some info to syslog DIALER_PID=$(cat /var/run/{{ ifname }}.pid) logger -t pppd[$DIALER_PID] "executing $0" @@ -45,7 +45,7 @@ echo 1 > /proc/sys/net/ipv6/conf/{{ ifname }}/autoconf systemctl start dhcp6c@{{ ifname }}.service {% endif %} -{% if default_route != 'none' -%} +{% if default_route != 'none' %} # See https://phabricator.vyos.net/T2248 & T2220. Determine if we are enslaved # to a VRF, this is needed to properly insert the default route. @@ -62,14 +62,14 @@ if [ -d /sys/class/net/{{ ifname }}/upper_* ]; then VRF_NAME="vrf ${VRF}" fi -{% if default_route == 'auto' -%} +{% if default_route == 'auto' %} # Only insert a new default route if there is no default route configured routes=$(vtysh -c "show running-config" | sed -n "/${SED_OPT}/,/!/p" | grep ::/0 | wc -l) if [ "$routes" -ne 0 ]; then exit 1 fi -{% elif default_route == 'force' -%} +{% elif default_route == 'force' %} # Retrieve current static default routes and remove it from the routing table vtysh -c "show running-config" | sed -n "/${SED_OPT}/,/!/p" | grep ::/0 | while read route ; do vtysh -c "conf t" ${VTY_OPT} -c "no ${route} ${VRF_NAME}" diff --git a/data/templates/pppoe/peer.tmpl b/data/templates/pppoe/peer.tmpl index e909843a5..dd4272a98 100644 --- a/data/templates/pppoe/peer.tmpl +++ b/data/templates/pppoe/peer.tmpl @@ -53,12 +53,12 @@ mru {{ mtu }} {{ "usepeerdns" if no_peer_dns is not defined }} -{% if ipv6 is defined and ipv6.enable is defined -%} +{% if ipv6 is defined and ipv6.enable is defined %} +ipv6 ipv6cp-use-ipaddr {% endif %} -{% if service_name is defined -%} +{% if service_name is defined %} rp_pppoe_service "{{ service_name }}" {% endif %} @@ -67,9 +67,9 @@ demand # See T2249. PPP default route options should only be set when in on-demand # mode. As soon as we are not in on-demand mode the default-route handling is # passed to the ip-up.d/ip-down.s scripts which is required for VRF support. -{% if 'auto' in default_route -%} +{% if 'auto' in default_route %} defaultroute -{% elif 'force' in default_route -%} +{% elif 'force' in default_route %} defaultroute replacedefaultroute {% endif %} diff --git a/data/templates/router-advert/radvd.conf.tmpl b/data/templates/router-advert/radvd.conf.tmpl index cebfc54b5..2fde78fec 100644 --- a/data/templates/router-advert/radvd.conf.tmpl +++ b/data/templates/router-advert/radvd.conf.tmpl @@ -43,5 +43,5 @@ interface {{ iface }} { }; {% endif %} }; -{% endfor -%} +{% endfor %} {% endif %} diff --git a/data/templates/salt-minion/minion.tmpl b/data/templates/salt-minion/minion.tmpl index 9369573a4..405fb9131 100644 --- a/data/templates/salt-minion/minion.tmpl +++ b/data/templates/salt-minion/minion.tmpl @@ -35,7 +35,7 @@ log_level: {{ log_level }} # Set the location of the salt master server, if the master server cannot be # resolved, then the minion will fail to start. master: -{% for host in master -%} +{% for host in master %} - {{ host }} {% endfor %} diff --git a/data/templates/snmp/usr.snmpd.conf.tmpl b/data/templates/snmp/usr.snmpd.conf.tmpl index 9c0337fa8..e2c5ec102 100644 --- a/data/templates/snmp/usr.snmpd.conf.tmpl +++ b/data/templates/snmp/usr.snmpd.conf.tmpl @@ -1,6 +1,6 @@ ### Autogenerated by snmp.py ### -{%- for u in v3_users %} +{% for u in v3_users %} {{ u.mode }}user {{ u.name }} -{%- endfor %} +{% endfor %} rwuser {{ vyos_user }} diff --git a/data/templates/snmp/var.snmpd.conf.tmpl b/data/templates/snmp/var.snmpd.conf.tmpl index 6cbc687ef..c779587df 100644 --- a/data/templates/snmp/var.snmpd.conf.tmpl +++ b/data/templates/snmp/var.snmpd.conf.tmpl @@ -1,14 +1,14 @@ ### Autogenerated by snmp.py ### # user -{%- for u in v3_users %} -{%- if u.authOID == 'none' %} +{% for u in v3_users %} +{% if u.authOID == 'none' %} createUser {{ u.name }} -{%- else %} +{% else %} usmUser 1 3 0x{{ v3_engineid }} "{{ u.name }}" "{{ u.name }}" NULL {{ u.authOID }} 0x{{ u.authMasterKey }} {{ u.privOID }} 0x{{ u.privMasterKey }} 0x -{%- endif %} -{%- endfor %} +{% endif %} +{% endfor %} createUser {{ vyos_user }} MD5 "{{ vyos_user_pass }}" DES -{%- if v3_engineid %} +{% if v3_engineid %} oldEngineID 0x{{ v3_engineid }} -{%- endif %} +{% endif %} diff --git a/data/templates/syslog/rsyslog.conf.tmpl b/data/templates/syslog/rsyslog.conf.tmpl index a610d132f..10fbb9d3c 100644 --- a/data/templates/syslog/rsyslog.conf.tmpl +++ b/data/templates/syslog/rsyslog.conf.tmpl @@ -1,14 +1,14 @@ ## generated by syslog.py ## ## file based logging -{% if files['global']['marker'] -%} +{% if files['global']['marker'] %} $ModLoad immark {% if files['global']['marker-interval'] %} $MarkMessagePeriod {{files['global']['marker-interval']}} {% endif %} -{% endif -%} -{% if files['global']['preserver_fqdn'] -%} +{% endif %} +{% if files['global']['preserver_fqdn'] %} $PreserveFQDN on -{% endif -%} +{% endif %} {% for file in files %} $outchannel {{file}},{{files[file]['log-file']}},{{files[file]['max-size']}},{{files[file]['action-on-max-size']}} {{files[file]['selectors']}} :omfile:${{file}} diff --git a/data/templates/vrf/vrf.conf.tmpl b/data/templates/vrf/vrf.conf.tmpl index 761b0bb6f..6d01d2b89 100644 --- a/data/templates/vrf/vrf.conf.tmpl +++ b/data/templates/vrf/vrf.conf.tmpl @@ -3,6 +3,6 @@ # Routing table ID to name mapping reference # id vrf name comment -{% for vrf in vrf_add -%} +{% for vrf in vrf_add %} {{ "%-10s" | format(vrf.table) }} {{ "%-16s" | format(vrf.name) }} # {{ vrf.description }} -{% endfor -%} +{% endfor %} diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/vrrp/keepalived.conf.tmpl index 210621681..d51522e45 100644 --- a/data/templates/vrrp/keepalived.conf.tmpl +++ b/data/templates/vrrp/keepalived.conf.tmpl @@ -9,9 +9,9 @@ global_defs { notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py } -{% for group in groups -%} +{% for group in groups %} -{% if group.health_check_script -%} +{% if group.health_check_script %} vrrp_script healthcheck_{{ group.name }} { script "{{ group.health_check_script }}" interval {{ group.health_check_interval }} @@ -22,9 +22,9 @@ vrrp_script healthcheck_{{ group.name }} { {% endif %} vrrp_instance {{ group.name }} { - {% if group.description -%} + {% if group.description %} # {{ group.description }} - {% endif -%} + {% endif %} state BACKUP interface {{ group.interface }} @@ -32,74 +32,74 @@ vrrp_instance {{ group.name }} { priority {{ group.priority }} advert_int {{ group.advertise_interval }} - {% if group.preempt -%} + {% if group.preempt %} preempt_delay {{ group.preempt_delay }} - {% else -%} + {% else %} nopreempt - {% endif -%} + {% endif %} - {% if group.peer_address -%} + {% if group.peer_address %} unicast_peer { {{ group.peer_address }} } - {% endif -%} + {% endif %} - {% if group.hello_source -%} - {%- if group.peer_address -%} + {% if group.hello_source %} + {% if group.peer_address %} unicast_src_ip {{ group.hello_source }} - {%- else -%} + {% else %} mcast_src_ip {{ group.hello_source }} - {%- endif %} - {% endif -%} + {% endif %} + {% endif %} - {% if group.use_vmac and group.peer_address -%} + {% if group.use_vmac and group.peer_address %} use_vmac {{group.interface}}v{{group.vrid}} vmac_xmit_base - {% elif group.use_vmac -%} + {% elif group.use_vmac %} use_vmac {{group.interface}}v{{group.vrid}} - {% endif -%} + {% endif %} - {% if group.auth_password -%} + {% if group.auth_password %} authentication { auth_pass "{{ group.auth_password }}" auth_type {{ group.auth_type }} } - {% endif -%} + {% endif %} virtual_ipaddress { - {% for addr in group.virtual_addresses -%} + {% for addr in group.virtual_addresses %} {{ addr }} - {% endfor -%} + {% endfor %} } - {% if group.virtual_addresses_excluded -%} + {% if group.virtual_addresses_excluded %} virtual_ipaddress_excluded { - {% for addr in group.virtual_addresses_excluded -%} + {% for addr in group.virtual_addresses_excluded %} {{ addr }} - {% endfor -%} + {% endfor %} } - {% endif -%} + {% endif %} - {% if group.health_check_script -%} + {% if group.health_check_script %} track_script { healthcheck_{{ group.name }} } - {% endif -%} + {% endif %} } -{% endfor -%} +{% endfor %} -{% for sync_group in sync_groups -%} +{% for sync_group in sync_groups %} vrrp_sync_group {{ sync_group.name }} { group { - {% for member in sync_group.members -%} + {% for member in sync_group.members %} {{ member }} - {% endfor -%} + {% endfor %} } - {% if sync_group.conntrack_sync -%} + {% if sync_group.conntrack_sync %} notify_master "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh master {{ sync_group.name }}" notify_backup "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh backup {{ sync_group.name }}" notify_fault "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh fault {{ sync_group.name }}" - {% endif -%} + {% endif %} } -{% endfor -%} +{% endfor %} diff --git a/data/templates/vyos-hostsd/hosts.tmpl b/data/templates/vyos-hostsd/hosts.tmpl index 566f9a5dd..be8692104 100644 --- a/data/templates/vyos-hostsd/hosts.tmpl +++ b/data/templates/vyos-hostsd/hosts.tmpl @@ -12,15 +12,15 @@ ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters -{% if hosts -%} +{% if hosts %} # From 'system static-host-mapping' and DHCP server -{%- for tag, taghosts in hosts.items() %} +{% for tag, taghosts in hosts.items() %} # {{ tag }} -{%- for host, hostprops in taghosts.items() %} -{%- if hostprops['address'] %} +{% for host, hostprops in taghosts.items() %} +{% if hostprops['address'] %} {{ hostprops['address'] }} {{ host }}{% for a in hostprops['aliases'] %} {{ a }}{% endfor %} -{%- endif %} -{%- endfor %} -{%- endfor %} -{%- endif %} +{% endif %} +{% endfor %} +{% endfor %} +{% endif %} diff --git a/data/templates/vyos-hostsd/resolv.conf.tmpl b/data/templates/vyos-hostsd/resolv.conf.tmpl index b920b2e5f..58a5f9312 100644 --- a/data/templates/vyos-hostsd/resolv.conf.tmpl +++ b/data/templates/vyos-hostsd/resolv.conf.tmpl @@ -1,26 +1,25 @@ ### Autogenerated by VyOS ### ### Do not edit, your changes will get overwritten ### -{#- the code below ensures the order of nameservers is determined first by #} +{# the code below ensures the order of nameservers is determined first by #} {# the order of tags, then by the order of nameservers within that tag #} -{%- for tag in name_server_tags_system %} -{%- if tag in name_servers %} +{% for tag in name_server_tags_system %} +{% if tag in name_servers %} # {{ tag }} -{%- for ns in name_servers[tag] %} +{% for ns in name_servers[tag] %} nameserver {{ ns }} -{%- endfor %} -{%- endif %} -{%- endfor %} +{% endfor %} +{% endif %} +{% endfor %} -{%- if domain_name %} +{% if domain_name %} domain {{ domain_name }} -{%- endif %} +{% endif %} {% for tag in name_server_tags_system %} -{%- if tag in search_domains %} +{% if tag in search_domains %} # {{ tag }} search {{ search_domains[tag]|join(' ') }} -{%- endif %} -{%- endfor %} - +{% endif %} +{% endfor %} diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl index 16d9f7c98..e66e3472b 100644 --- a/data/templates/wifi/hostapd.conf.tmpl +++ b/data/templates/wifi/hostapd.conf.tmpl @@ -451,14 +451,6 @@ macaddr_acl=0 max_num_sta={{ max_stations }} {% endif %} -{% if wds is defined %} -# WDS (4-address frame) mode with per-station virtual interfaces -# (only supported with driver=nl80211) -# This mode allows associated stations to use 4-address frames to allow layer 2 -# bridging to be used. -wds_sta=1 -{% endif %} - {% if isolate_stations is defined %} # Client isolation can be used to prevent low-level bridging of frames between # associated stations in the BSS. By default, this bridging is allowed. diff --git a/data/templates/wwan/ip-pre-up.script.tmpl b/data/templates/wwan/ip-pre-up.script.tmpl index efc065bad..199150947 100644 --- a/data/templates/wwan/ip-pre-up.script.tmpl +++ b/data/templates/wwan/ip-pre-up.script.tmpl @@ -17,7 +17,7 @@ logger -t pppd[$DIALER_PID] "executing $0" echo "{{ description }}" > /sys/class/net/{{ ifname }}/ifalias -{% if vrf -%} +{% if vrf %} logger -t pppd[$DIALER_PID] "configuring interface {{ ifname }} for VRF {{ vrf }}" ip link set dev {{ ifname }} master {{ vrf }} {% endif %} diff --git a/debian/control b/debian/control index a290952a3..5e9e708b4 100644 --- a/debian/control +++ b/debian/control @@ -114,7 +114,6 @@ Depends: vyos-utils, wide-dhcpv6-client, wireguard-tools, - wireguard-modules, wireless-regdb, wpasupplicant (>= 0.6.7) Description: VyOS configuration scripts and data diff --git a/interface-definitions/dhcp-relay.xml.in b/interface-definitions/dhcp-relay.xml.in index b83402aa1..8c95239d9 100644 --- a/interface-definitions/dhcp-relay.xml.in +++ b/interface-definitions/dhcp-relay.xml.in @@ -35,6 +35,7 @@ </constraint> <constraintErrorMessage>hop-count must be a value between 1 and 255</constraintErrorMessage> </properties> + <defaultValue>10</defaultValue> </leafNode> <leafNode name="max-size"> <properties> @@ -72,9 +73,10 @@ <description>discard packet (default action if giaddr not set in packet)</description> </valueHelp> <constraint> - <regex>(append|replace|forward|discard)</regex> + <regex>^(append|replace|forward|discard)$</regex> </constraint> </properties> + <defaultValue>forward</defaultValue> </leafNode> </children> </node> diff --git a/interface-definitions/dhcpv6-relay.xml.in b/interface-definitions/dhcpv6-relay.xml.in index 0beb09d05..308f94a01 100644 --- a/interface-definitions/dhcpv6-relay.xml.in +++ b/interface-definitions/dhcpv6-relay.xml.in @@ -43,6 +43,7 @@ </constraint> <constraintErrorMessage>max-hop-count must be a value between 1 and 255</constraintErrorMessage> </properties> + <defaultValue>10</defaultValue> </leafNode> <tagNode name="upstream-interface"> <properties> diff --git a/interface-definitions/igmp-proxy.xml.in b/interface-definitions/igmp-proxy.xml.in index 74fec6b48..b9c52794f 100644 --- a/interface-definitions/igmp-proxy.xml.in +++ b/interface-definitions/igmp-proxy.xml.in @@ -44,7 +44,7 @@ </leafNode> <leafNode name="role"> <properties> - <help>Role of this IGMP interface</help> + <help>IGMP interface role (default: downstream)</help> <completionHelp> <list>upstream downstream disabled</list> </completionHelp> @@ -61,13 +61,14 @@ <description>Disabled interface</description> </valueHelp> <constraint> - <regex>(upstream|downstream|disabled)</regex> + <regex>^(upstream|downstream|disabled)$</regex> </constraint> </properties> + <defaultValue>downstream</defaultValue> </leafNode> <leafNode name="threshold"> <properties> - <help>TTL threshold</help> + <help>TTL threshold (default: 1)</help> <valueHelp> <format>1-255</format> <description>TTL threshold for the interfaces (default: 1)</description> @@ -75,8 +76,9 @@ <constraint> <validator name="numeric" argument="--range 1-255"/> </constraint> - <constraintErrorMessage>threshold must be between 1 and 255</constraintErrorMessage> + <constraintErrorMessage>Threshold must be between 1 and 255</constraintErrorMessage> </properties> + <defaultValue>1</defaultValue> </leafNode> <leafNode name="whitelist"> <properties> diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i index f91834ae0..e70cfd796 100644 --- a/interface-definitions/include/nat-rule.xml.i +++ b/interface-definitions/include/nat-rule.xml.i @@ -290,6 +290,7 @@ <validator name="ip-protocol"/> </constraint> </properties> + <defaultValue>all</defaultValue> </leafNode> <node name="source"> <properties> diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index c3f178d59..b322374b3 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -39,7 +39,6 @@ <script>${vyos_completion_dir}/list_local.py</script> </completionHelp> <constraint> - <!-- does it need fixing/changing to be more restrictive ? --> <validator name="ip-address"/> </constraint> </properties> @@ -104,7 +103,7 @@ <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> <constraint> - <regex>(en|eth|br|bond|gnv|vxlan|wg|tun)[0-9]+</regex> + <regex>^(en|eth|br|bond|gnv|vxlan|wg|tun)[0-9]+$</regex> </constraint> </properties> </leafNode> @@ -112,36 +111,40 @@ <properties> <help>Encapsulation of this tunnel interface</help> <completionHelp> - <list>gre gre-bridge ipip sit ipip6 ip6ip6 ip6gre</list> + <list>gre gre-bridge ip6gre ip6ip6 ipip ipip6 sit</list> </completionHelp> <valueHelp> + <format>gre</format> + <description>Generic Routing Encapsulation</description> + </valueHelp> + <valueHelp> <format>gre-bridge</format> <description>Generic Routing Encapsulation bridge interface</description> </valueHelp> <valueHelp> - <format>ipip</format> - <description>IP in IP encapsulation</description> + <format>ip6gre</format> + <description>GRE over IPv6 network</description> </valueHelp> <valueHelp> - <format>sit</format> - <description>Simple Internet Transition encapsulation</description> + <format>ip6ip6</format> + <description>IP6 in IP6 encapsulation</description> </valueHelp> <valueHelp> - <format>ipip6</format> - <description>IP in IP6 encapsulation</description> + <format>ipip</format> + <description>IP in IP encapsulation</description> </valueHelp> <valueHelp> - <format>ip6ip6</format> - <description>IP6 in IP6 encapsulation</description> + <format>ipip6</format> + <description>IP in IP6 encapsulation</description> </valueHelp> <valueHelp> - <format>ip6gre</format> - <description>GRE over IPv6 network</description> + <format>sit</format> + <description>Simple Internet Transition encapsulation</description> </valueHelp> <constraint> - <regex>(gre|gre-bridge|ipip|sit|ipip6|ip6ip6|ip6gre)</regex> + <regex>^(gre|gre-bridge|ip6gre|ip6ip6|ipip|ipip6|sit)$</regex> </constraint> - <constraintErrorMessage>Must be one of 'gre' 'gre-bridge' 'ipip' 'sit' 'ipip6' 'ip6ip6' 'ip6gre'</constraintErrorMessage> + <constraintErrorMessage>Invalid encapsulation, must be one of: gre, gre-bridge, ipip, sit, ipip6, ip6ip6, ip6gre</constraintErrorMessage> </properties> </leafNode> <leafNode name="multicast"> @@ -159,7 +162,7 @@ <description>Disable Multicast (default)</description> </valueHelp> <constraint> - <regex>(enable|disable)</regex> + <regex>^(enable|disable)$</regex> </constraint> <constraintErrorMessage>Must be 'disable' or 'enable'</constraintErrorMessage> </properties> @@ -186,6 +189,7 @@ </constraint> <constraintErrorMessage>TTL must be between 0 and 255</constraintErrorMessage> </properties> + <defaultValue>255</defaultValue> </leafNode> <leafNode name="tos"> <properties> @@ -199,6 +203,7 @@ </constraint> <constraintErrorMessage>TOS must be between 0 and 99</constraintErrorMessage> </properties> + <defaultValue>inherit</defaultValue> </leafNode> <leafNode name="key"> <properties> @@ -232,6 +237,7 @@ </constraint> <constraintErrorMessage>key must be between 0-255</constraintErrorMessage> </properties> + <defaultValue>4</defaultValue> </leafNode> <leafNode name="flowlabel"> <properties> @@ -245,6 +251,7 @@ </constraint> <constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage> </properties> + <defaultValue>inherit</defaultValue> </leafNode> <leafNode name="hoplimit"> <properties> @@ -258,6 +265,7 @@ </constraint> <constraintErrorMessage>hoplimit must be between 0-255</constraintErrorMessage> </properties> + <defaultValue>64</defaultValue> </leafNode> <leafNode name="tclass"> <properties> @@ -271,6 +279,7 @@ </constraint> <constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage> </properties> + <defaultValue>inherit</defaultValue> </leafNode> </children> </node> diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index aa63e4ac7..84f7803a0 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -22,6 +22,7 @@ #include <include/interface-vrf.xml.i> #include <include/port-number.xml.i> #include <include/interface-mtu-68-16000.xml.i> + #include <include/interface-ipv6-options.xml.i> <leafNode name="fwmark"> <properties> <help>A 32-bit fwmark value set on all outgoing packets</help> diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index fdea1e3ab..6b238e313 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -771,12 +771,6 @@ </leafNode> #include <include/vif.xml.i> #include <include/vif-s.xml.i> - <leafNode name="wds"> - <properties> - <help>Enable WDS (Wireless Distribution System)</help> - <valueless/> - </properties> - </leafNode> </children> </tagNode> </children> diff --git a/interface-definitions/ntp.xml.in b/interface-definitions/ntp.xml.in index 6070cafe0..6a8eb0818 100644 --- a/interface-definitions/ntp.xml.in +++ b/interface-definitions/ntp.xml.in @@ -20,6 +20,12 @@ <valueless/> </properties> </leafNode> + <leafNode name="pool"> + <properties> + <help>Associate with a number of remote servers</help> + <valueless/> + </properties> + </leafNode> <leafNode name="preempt"> <properties> <help>Specifies the association as preemptable rather than the default persistent</help> diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in new file mode 100644 index 000000000..5536c71c8 --- /dev/null +++ b/interface-definitions/policy-local-route.xml.in @@ -0,0 +1,67 @@ +<?xml version="1.0"?> +<!-- Policy local-route --> +<interfaceDefinition> + <node name="policy"> + <children> + <node name="local-route" owner="${vyos_conf_scripts_dir}/policy-local-route.py"> + <properties> + <help>IPv4 policy route of local traffic</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>Policy local-route rule set number</help> + <valueHelp> + <!-- table main with prio 32766 --> + <format><1-32765></format> + <description>Local-route rule number (1-219)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-32765"/> + </constraint> + </properties> + <children> + <node name="set"> + <properties> + <help>Packet modifications</help> + </properties> + <children> + <leafNode name="table"> + <properties> + <help>Routing table to forward packet with</help> + <valueHelp> + <format><1-200></format> + <description>Table number</description> + </valueHelp> + <completionHelp> + <list>main</list> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + <leafNode name="source"> + <properties> + <help>Source address or prefix</help> + <valueHelp> + <format>ipv4</format> + <description>Address to match against</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>Prefix to match against</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/protocols-mpls.xml.in b/interface-definitions/protocols-mpls.xml.in index 4df2be4e7..16286f2c4 100644 --- a/interface-definitions/protocols-mpls.xml.in +++ b/interface-definitions/protocols-mpls.xml.in @@ -375,6 +375,15 @@ </leafNode> </children> </node> + <leafNode name="interface"> + <properties> + <help>Enable MPLS packet processing on interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + <multi/> + </properties> + </leafNode> </children> </node> </children> diff --git a/interface-definitions/system-options.xml.in b/interface-definitions/system-option.xml.in index 12e4044af..26b78c8a4 100644 --- a/interface-definitions/system-options.xml.in +++ b/interface-definitions/system-option.xml.in @@ -2,35 +2,29 @@ <interfaceDefinition> <node name="system"> <children> - <node name="options" owner="${vyos_conf_scripts_dir}/system-options.py"> + <node name="option" owner="${vyos_conf_scripts_dir}/system-option.py"> <properties> <help>System Options</help> <priority>9999</priority> </properties> <children> - <leafNode name="beep-if-fully-booted"> + <leafNode name="ctrl-alt-delete"> <properties> - <help>plays sound via system speaker when you can login</help> - <valueless/> - </properties> - </leafNode> - <leafNode name="ctrl-alt-del-action"> - <properties> - <help>Ctrl-Alt-Delete action</help> + <help>System action on Ctrl-Alt-Delete keystroke</help> <completionHelp> <list>ignore reboot poweroff</list> </completionHelp> <valueHelp> <format>ignore</format> - <description>Ignore Ctrl-Alt-Delete</description> + <description>Ignore key sequence</description> </valueHelp> <valueHelp> <format>reboot</format> - <description>Reboot VyOS</description> + <description>Reboot system</description> </valueHelp> <valueHelp> <format>poweroff</format> - <description>Poweroff VyOS</description> + <description>Poweroff system</description> </valueHelp> <constraint> <regex>^(ignore|reboot|poweroff)$</regex> @@ -38,7 +32,40 @@ <constraintErrorMessage>Must be ignore, reboot, or poweroff</constraintErrorMessage> </properties> </leafNode> - <leafNode name="performance"> + <leafNode name="keyboard-layout"> + <properties> + <help>System keyboard layout, type ISO2</help> + <completionHelp> + <list>us fr de fi no dk</list> + </completionHelp> + <valueHelp> + <format>us</format> + <description>United States of America</description> + </valueHelp> + <valueHelp> + <format>fr</format> + <description>France</description> + </valueHelp> + <valueHelp> + <format>de</format> + <description>Germany</description> + </valueHelp> + <valueHelp> + <format>fi</format> + <description>Finland</description> + </valueHelp> + <valueHelp> + <format>no</format> + <description>Norway</description> + </valueHelp> + <valueHelp> + <format>dk</format> + <description>Denmark</description> + </valueHelp> + </properties> + <defaultValue>us</defaultValue> + </leafNode> + <leafNode name="performance"> <properties> <help>Tune system performance</help> <completionHelp> @@ -80,38 +107,11 @@ #include <include/source-address-ipv4-ipv6.xml.i> </children> </node> - <leafNode name="keyboard-layout"> + <leafNode name="startup-beep"> <properties> - <help>System keyboard layout, type ISO2</help> - <completionHelp> - <list>us fr de fi no dk</list> - </completionHelp> - <valueHelp> - <format>us</format> - <description>United States of America</description> - </valueHelp> - <valueHelp> - <format>fr</format> - <description>France</description> - </valueHelp> - <valueHelp> - <format>de</format> - <description>Germany</description> - </valueHelp> - <valueHelp> - <format>fi</format> - <description>Finland</description> - </valueHelp> - <valueHelp> - <format>no</format> - <description>Norway</description> - </valueHelp> - <valueHelp> - <format>dk</format> - <description>Denmark</description> - </valueHelp> + <help>plays sound via system speaker when you can login</help> + <valueless/> </properties> - <defaultValue>us</defaultValue> </leafNode> </children> </node> diff --git a/op-mode-definitions/force-arp.xml b/op-mode-definitions/force-arp.xml index c7bcad413..f9f7c7643 100644 --- a/op-mode-definitions/force-arp.xml +++ b/op-mode-definitions/force-arp.xml @@ -72,6 +72,30 @@ </tagNode> </children> </node> + <node name="duplicate"> + <properties> + <help>Send ARP for DAD detection</help> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Send ARP for DAD detection on specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script> + </completionHelp> + </properties> + <children> + <tagNode name="address"> + <properties> + <help>Send ARP for DAD detection for specified address</help> + </properties> + <command>sudo /usr/bin/arping -I $5 -c 1 -D $7</command> + </tagNode> + </children> + </tagNode> + </children> + </node> + </children> </node> </children> diff --git a/op-mode-definitions/restart.xml b/op-mode-definitions/restart.xml new file mode 100644 index 000000000..c74ec9013 --- /dev/null +++ b/op-mode-definitions/restart.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="restart"> + <properties> + <help>Restart individual service</help> + </properties> + </node> +</interfaceDefinition> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index b14f96364..cdcd3f9ea 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -439,13 +439,13 @@ def get_accel_dict(config, base, chap_secrets): # options which we need to update into the dictionary retrived. default_values = defaults(base) - # defaults include RADIUS server specifics per TAG node which need to be - # added to individual RADIUS servers instead - so we can simply delete them + # T2665: defaults include RADIUS server specifics per TAG node which need to + # be added to individual RADIUS servers instead - so we can simply delete them if dict_search('authentication.radius.server', default_values): del default_values['authentication']['radius']['server'] - # defaults include static-ip address per TAG node which need to be added to - # individual local users instead - so we can simply delete them + # T2665: defaults include static-ip address per TAG node which need to be + # added to individual local users instead - so we can simply delete them if dict_search('authentication.local_users.username', default_values): del default_values['authentication']['local_users']['username'] @@ -471,6 +471,7 @@ def get_accel_dict(config, base, chap_secrets): # Add individual RADIUS server default values if dict_search('authentication.radius.server', dict): + # T2665 default_values = defaults(base + ['authentication', 'radius', 'server']) for server in dict_search('authentication.radius.server', dict): @@ -484,6 +485,7 @@ def get_accel_dict(config, base, chap_secrets): # Add individual local-user default values if dict_search('authentication.local_users.username', dict): + # T2665 default_values = defaults(base + ['authentication', 'local-users', 'username']) for username in dict_search('authentication.local_users.username', dict): diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 7c77e050a..d0d5da881 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -157,7 +157,7 @@ class BridgeIf(Interface): >>> BridgeIf('br0').set_stp(1) """ self.set_interface('stp', state) - + def set_vlan_filter(self, state): """ Set bridge Vlan Filter state. 0 -> Vlan Filter disabled, 1 -> Vlan Filter enabled @@ -192,12 +192,11 @@ class BridgeIf(Interface): >>> BridgeIf('br0').add_port('eth0') >>> BridgeIf('br0').add_port('eth1') """ - # Bridge port handling of wireless interfaces is done by hostapd. - if 'wlan' in interface: - return - - return self.set_interface('add_port', interface) - + try: + return self.set_interface('add_port', interface) + except: + from vyos import ConfigError + raise ConfigError('Error: Device does not allow enslaving to a bridge.') def del_port(self, interface): """ @@ -217,7 +216,7 @@ class BridgeIf(Interface): # call base class first super().update(config) - + ifname = config['ifname'] # Set ageing time @@ -255,7 +254,7 @@ class BridgeIf(Interface): if member in interfaces(): self.del_port(member) vlan_filter = 0 - + vlan_del = set() vlan_add = set() @@ -286,9 +285,9 @@ class BridgeIf(Interface): if 'priority' in interface_config: value = interface_config.get('priority') lower.set_path_priority(value) - + tmp = dict_search('native_vlan_removed', interface_config) - + for vlan_id in (tmp or []): cmd = f'bridge vlan del dev {interface} vid {vlan_id}' self._cmd(cmd) @@ -296,37 +295,43 @@ class BridgeIf(Interface): self._cmd(cmd) vlan_del.add(vlan_id) vlan_add.add(1) - + tmp = dict_search('allowed_vlan_removed', interface_config) - - + + for vlan_id in (tmp or []): cmd = f'bridge vlan del dev {interface} vid {vlan_id}' self._cmd(cmd) vlan_del.add(vlan_id) - + if 'native_vlan' in interface_config: vlan_filter = 1 cmd = f'bridge vlan del dev {interface} vid 1' self._cmd(cmd) - vlan_del.add(1) vlan_id = interface_config['native_vlan'] + if vlan_id != 1: + vlan_del.add(1) cmd = f'bridge vlan add dev {interface} vid {vlan_id} pvid untagged master' self._cmd(cmd) vlan_add.add(vlan_id) - else: - cmd = f'bridge vlan del dev {interface} vid 1' - self._cmd(cmd) - vlan_del.add(1) - + if 'allowed_vlan' in interface_config: vlan_filter = 1 + + if vlan_filter: + if 'native_vlan' not in interface_config: + cmd = f'bridge vlan del dev {interface} vid 1' + self._cmd(cmd) + + if 'allowed_vlan' in interface_config: for vlan in interface_config['allowed_vlan']: cmd = f'bridge vlan add dev {interface} vid {vlan} master' self._cmd(cmd) vlan_add.add(vlan) - - + + + + for vlan in vlan_del: if isinstance(vlan,str) and vlan.isnumeric(): if int(vlan) == 1: @@ -340,15 +345,15 @@ class BridgeIf(Interface): else: cmd = f'bridge vlan del dev {ifname} vid {vlan} self' self._cmd(cmd) - + for vlan in vlan_add: cmd = f'bridge vlan add dev {ifname} vid {vlan} self' self._cmd(cmd) - + # enable/disable Vlan Filter self.set_vlan_filter(vlan_filter) - - + + # Enable/Disable of an interface must always be done at the end of the # derived class to make use of the ref-counting set_admin_state() # function. We will only enable the interface if 'up' was called as diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 893623284..43cd7220a 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -890,9 +890,9 @@ class Interface(Control): self._config = dict_merge(tmp, self._config) render(options_file, 'dhcp-client/daemon-options.tmpl', - self._config, trim_blocks=True) + self._config) render(config_file, 'dhcp-client/ipv4.tmpl', - self._config, trim_blocks=True) + self._config) # 'up' check is mandatory b/c even if the interface is A/D, as soon as # the DHCP client is started the interface will be placed in u/u state. @@ -919,7 +919,7 @@ class Interface(Control): if enable and 'disable' not in self._config: render(config_file, 'dhcp-client/ipv6.tmpl', - self._config, trim_blocks=True) + self._config) # We must ignore any return codes. This is required to enable DHCPv6-PD # for interfaces which are yet not up and running. @@ -942,6 +942,15 @@ class Interface(Control): # method to apply()? self._config = config + # Change interface MAC address - re-set to real hardware address (hw-id) + # if custom mac is removed. Skip if bond member. + if 'is_bond_member' not in config: + mac = config.get('hw_id') + if 'mac' in config: + mac = config.get('mac') + if mac: + self.set_mac(mac) + # Update interface description self.set_alias(config.get('description', '')) @@ -1058,15 +1067,6 @@ class Interface(Control): for addr in tmp: self.del_ipv6_eui64_address(addr) - # Change interface MAC address - re-set to real hardware address (hw-id) - # if custom mac is removed. Skip if bond member. - if 'is_bond_member' not in config: - mac = config.get('hw_id') - if 'mac' in config: - mac = config.get('mac') - if mac: - self.set_mac(mac) - # Manage IPv6 link-local addresses tmp = dict_search('ipv6.address.no_default_link_local', config) # we must check explicitly for None type as if the key is set we will diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index 4122d1a2f..00dc36420 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -1,4 +1,4 @@ -# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2020 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -18,8 +18,11 @@ from copy import deepcopy +from netaddr import EUI +from netaddr import mac_unix_expanded +from random import getrandbits + from vyos.ifconfig.interface import Interface -from vyos.ifconfig.afi import IP4, IP6 from vyos.validate import assert_list def enable_to_on(value): @@ -42,7 +45,6 @@ class _Tunnel(Interface): **{ 'section': 'tunnel', 'prefixes': ['tun',], - 'bridgeable': False, }, } @@ -61,68 +63,73 @@ class _Tunnel(Interface): }, }} - # use for "options" and "updates" - # If an key is only in the options list, it can only be set at creation time - # the create comand will only be make using the key in options - - # If an option is in the updates list, it can be updated - # upon, the creation, all key not yet applied will be updated - - # multicast/allmulticast can not be part of the create command - - # options matrix: - # with ip = 4, we have multicast - # wiht ip = 6, nothing - # with tunnel = 4, we have tos, ttl, key - # with tunnel = 6, we have encaplimit, hoplimit, tclass, flowlabel - - # TODO: For multicast, it is allowed on IP6IP6 and Sit6RD - # TODO: to match vyatta but it should be checked for correctness - - updates = [] - - create = '' - change = '' - delete = '' - - ip = [] # AFI of the families which can be used in the tunnel - tunnel = 0 # invalid - need to be set by subclasses - def __init__(self, ifname, **config): self.config = deepcopy(config) if config else {} super().__init__(ifname, **config) def _create(self): + create = 'ip tunnel add {ifname} mode {type}' + # add " option-name option-name-value ..." for all options set options = " ".join(["{} {}".format(k, self.config[k]) for k in self.options if k in self.config and self.config[k]]) - self._cmd('{} {}'.format(self.create.format(**self.config), options)) + self._cmd('{} {}'.format(create.format(**self.config), options)) self.set_admin_state('down') - def _delete(self): - self.set_admin_state('down') - cmd = self.delete.format(**self.config) - return self._cmd(cmd) + def change_options(self): + change = 'ip tunnel cha {ifname} mode {type}' - def set_interface(self, option, value): - try: - return Interface.set_interface(self, option, value) - except Exception: - pass - - if value == '': - # remove the value so that it is not used - self.config.pop(option, '') - - if self.change: - self._cmd('{} {} {}'.format( - self.change.format(**self.config), option, value)) - return True + # add " option-name option-name-value ..." for all options set + options = " ".join(["{} {}".format(k, self.config[k]) + for k in self.options if k in self.config and self.config[k]]) + self._cmd('{} {}'.format(change.format(**self.config), options)) @classmethod def get_config(cls): return dict(zip(cls.options, ['']*len(cls.options))) + def get_mac(self): + """ + Get current interface MAC (Media Access Contrl) address used. + + NOTE: Tunnel interfaces have no "MAC" address by default. The content + of the 'address' file in /sys/class/net/device contains the + local-ip thus we generate a random MAC address instead + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_mac() + '00:50:ab:cd:ef:00' + """ + # we choose 40 random bytes for the MAC address, this gives + # us e.g. EUI('00-EA-EE-D6-A3-C8') or EUI('00-41-B9-0D-F2-2A') + tmp = EUI(getrandbits(48)).value + # set locally administered bit in MAC address + tmp |= 0xf20000000000 + # convert integer to "real" MAC address representation + mac = EUI(hex(tmp).split('x')[-1]) + # change dialect to use : as delimiter instead of - + mac.dialect = mac_unix_expanded + return str(mac) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # call base class first + super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) class GREIf(_Tunnel): """ @@ -134,27 +141,8 @@ class GREIf(_Tunnel): https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_gre.c """ - definition = { - **_Tunnel.definition, - **{ - 'bridgeable': True, - }, - } - - ip = [IP4, IP6] - tunnel = IP4 - default = {'type': 'gre'} - required = ['local', ] # mGRE is a GRE without remote endpoint - options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key'] - updates = ['local', 'remote', 'dev', 'ttl', 'tos', - 'mtu', 'multicast', 'allmulticast'] - - create = 'ip tunnel add {ifname} mode {type}' - change = 'ip tunnel cha {ifname}' - delete = 'ip tunnel del {ifname}' - # GreTap also called GRE Bridge class GRETapIf(_Tunnel): @@ -173,19 +161,8 @@ class GRETapIf(_Tunnel): }, } - ip = [IP4, ] - tunnel = IP4 - default = {'type': 'gretap'} - required = ['local', ] - options = ['local', 'remote', 'ttl',] - updates = ['mtu', ] - - create = 'ip link add {ifname} type {type}' - change = '' - delete = 'ip link del {ifname}' - class IP6GREIf(_Tunnel): """ @@ -196,30 +173,9 @@ class IP6GREIf(_Tunnel): https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_gre6.c """ - ip = [IP4, IP6] - tunnel = IP6 - default = {'type': 'ip6gre'} - required = ['local', 'remote'] - options = ['local', 'remote', 'dev', 'encaplimit', 'hoplimit', 'tclass', 'flowlabel'] - updates = ['local', 'remote', 'dev', 'encaplimit', - 'hoplimit', 'tclass', 'flowlabel', - 'mtu', 'multicast', 'allmulticast'] - - create = 'ip tunnel add {ifname} mode {type}' - change = 'ip tunnel cha {ifname} mode {type}' - delete = 'ip tunnel del {ifname}' - - # using "ip tunnel change" without using "mode" causes errors - # sudo ip tunnel add tun100 mode ip6gre local ::1 remote 1::1 - # sudo ip tunnel cha tun100 hoplimit 100 - # *** stack smashing detected ** *: < unknown > terminated - # sudo ip tunnel cha tun100 local: : 2 - # Error: an IP address is expected rather than "::2" - # works if mode is explicit - class IPIPIf(_Tunnel): """ @@ -232,20 +188,8 @@ class IPIPIf(_Tunnel): # IPIP does not allow to pass multicast, unlike GRE # but the interface itself can be set with multicast - ip = [IP4,] - tunnel = IP4 - default = {'type': 'ipip'} - required = ['local', 'remote'] - options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key'] - updates = ['local', 'remote', 'dev', 'ttl', 'tos', - 'mtu', 'multicast', 'allmulticast'] - - create = 'ip tunnel add {ifname} mode {type}' - change = 'ip tunnel cha {ifname}' - delete = 'ip tunnel del {ifname}' - class IPIP6If(_Tunnel): """ @@ -255,22 +199,9 @@ class IPIP6If(_Tunnel): https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_ip6tnl.c """ - ip = [IP4,] - tunnel = IP6 - default = {'type': 'ipip6'} - required = ['local', 'remote'] - options = ['local', 'remote', 'dev', 'encaplimit', 'hoplimit', 'tclass', 'flowlabel'] - updates = ['local', 'remote', 'dev', 'encaplimit', - 'hoplimit', 'tclass', 'flowlabel', - 'mtu', 'multicast', 'allmulticast'] - - create = 'ip -6 tunnel add {ifname} mode {type}' - change = 'ip -6 tunnel cha {ifname}' - delete = 'ip -6 tunnel del {ifname}' - class IP6IP6If(IPIP6If): """ @@ -279,9 +210,6 @@ class IP6IP6If(IPIP6If): For more information please refer to: https://tools.ietf.org/html/rfc2473 """ - - ip = [IP6,] - default = {'type': 'ip6ip6'} @@ -293,20 +221,8 @@ class SitIf(_Tunnel): https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_iptnl.c """ - ip = [IP6, IP4] - tunnel = IP4 - default = {'type': 'sit'} - required = ['local', 'remote'] - options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key'] - updates = ['local', 'remote', 'dev', 'ttl', 'tos', - 'mtu', 'multicast', 'allmulticast'] - - create = 'ip tunnel add {ifname} mode {type}' - change = 'ip tunnel cha {ifname}' - delete = 'ip tunnel del {ifname}' - class Sit6RDIf(SitIf): """ @@ -314,15 +230,8 @@ class Sit6RDIf(SitIf): https://en.wikipedia.org/wiki/IPv6_rapid_deployment """ - - ip = [IP6,] - - required = ['remote', '6rd-prefix'] - # TODO: check if key can really be used with 6RD options = ['remote', 'ttl', 'tos', 'key', '6rd-prefix', '6rd-relay-prefix'] - updates = ['remote', 'ttl', 'tos', - 'mtu', 'multicast', 'allmulticast'] def _create(self): # do not call _Tunnel.create, building fully here diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index da3bd4e89..9ee798ee8 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -17,6 +17,9 @@ import os import time from datetime import timedelta +from netaddr import EUI +from netaddr import mac_unix_expanded +from random import getrandbits from hurry.filesize import size from hurry.filesize import alternative @@ -162,13 +165,37 @@ class WireGuardIf(Interface): **{ 'section': 'wireguard', 'prefixes': ['wg', ], - 'bridgeable': True, + 'bridgeable': False, } } options = Interface.options + \ ['port', 'private_key', 'pubkey', 'psk', 'allowed_ips', 'fwmark', 'endpoint', 'keepalive'] + def get_mac(self): + """ + Get current interface MAC (Media Access Contrl) address used. + + NOTE: Tunnel interfaces have no "MAC" address by default. The content + of the 'address' file in /sys/class/net/device contains the + local-ip thus we generate a random MAC address instead + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').get_mac() + '00:50:ab:cd:ef:00' + """ + # we choose 40 random bytes for the MAC address, this gives + # us e.g. EUI('00-EA-EE-D6-A3-C8') or EUI('00-41-B9-0D-F2-2A') + tmp = EUI(getrandbits(48)).value + # set locally administered bit in MAC address + tmp |= 0xf20000000000 + # convert integer to "real" MAC address representation + mac = EUI(hex(tmp).split('x')[-1]) + # change dialect to use : as delimiter instead of - + mac.dialect = mac_unix_expanded + return str(mac) + def update(self, config): """ General helper function which works on a dictionary retrived by get_config_dict(). It's main intention is to consolidate the scattered @@ -221,7 +248,7 @@ class WireGuardIf(Interface): # Endpoint configuration is optional if {'address', 'port'} <= set(peer): - if is_ipv6(config['address']): + if is_ipv6(peer['address']): cmd += ' endpoint [{address}]:{port}' else: cmd += ' endpoint {address}:{port}' diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py index deca68bf0..37703d242 100644 --- a/python/vyos/ifconfig/wireless.py +++ b/python/vyos/ifconfig/wireless.py @@ -23,10 +23,8 @@ class WiFiIf(Interface): default = { 'type': 'wifi', - 'phy': '', - 'wds': 'off', + 'phy': 'phy0' } - definition = { **Interface.definition, **{ @@ -35,19 +33,12 @@ class WiFiIf(Interface): 'bridgeable': True, } } - options = Interface.options + \ ['phy', 'op_mode'] - _command_set = {**Interface._command_set, **{ - '4addr': { - 'shellcmd': 'iw dev {ifname} set 4addr {value}', - }, - }} - def _create(self): # all interfaces will be added in monitor mode - cmd = 'iw phy {phy} interface add {ifname} type monitor 4addr {wds}' \ + cmd = 'iw phy {phy} interface add {ifname} type monitor' \ .format(**self.config) self._cmd(cmd) @@ -59,20 +50,28 @@ class WiFiIf(Interface): .format(**self.config) self._cmd(cmd) - def set_4aadr_mode(self, state): - return self.set_interface('4addr', state) - def update(self, config): """ General helper function which works on a dictionary retrived by get_config_dict(). It's main intention is to consolidate the scattered interface setup code and provide a single point of entry when workin on any interface. """ - self.set_4aadr_mode('on' if 'wds' in config else 'off') + # We can not call add_to_bridge() until wpa_supplicant is running, thus + # we will remove the key from the config dict and react to this specal + # case in thie derived class. + # re-add ourselves to any bridge we might have fallen out of + bridge_member = '' + if 'is_bridge_member' in config: + bridge_member = config['is_bridge_member'] + del config['is_bridge_member'] # call base class first super().update(config) + # re-add ourselves to any bridge we might have fallen out of + if bridge_member: + self.add_to_bridge(bridge_member) + # Enable/Disable of an interface must always be done at the end of the # derived class to make use of the ref-counting set_admin_state() # function. We will only enable the interface if 'up' was called as diff --git a/python/vyos/limericks.py b/python/vyos/limericks.py index e03ccd32b..3c6744816 100644 --- a/python/vyos/limericks.py +++ b/python/vyos/limericks.py @@ -18,18 +18,18 @@ import random limericks = [ """ -A programmer who's name was Searle -Once wrote a long program in Perl. -Despite very few quirks -No one got how it works, -Not even the interpreter. +A programmer whose name was Searle +once wrote a long program in Perl. +Despite very few quirks, +no one got how it works. +Not even the interpreter perl(1). """, """ There was a young lady of Maine -Who set up IPsec VPN. +who set up IPsec VPN. Problems didn't arise -'til other vendors' device +till other vendors' device had to add she to that VPN. """, @@ -45,7 +45,7 @@ to get the damn build scripts to work. A network admin from Hong Kong knew MPPE cipher's not strong. But he was behind NAT, -so he put up we that, +so he put up with that, sad network admin from Hong Kong. """, diff --git a/python/vyos/template.py b/python/vyos/template.py index 58ba75972..b31f5bea2 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -18,24 +18,25 @@ import os from jinja2 import Environment from jinja2 import FileSystemLoader -from vyos.defaults import directories -from vyos.util import chmod, chown, makedir +from vyos.defaults import directories +from vyos.util import chmod +from vyos.util import chown +from vyos.util import makedir # Holds template filters registered via register_filter() _FILTERS = {} - -# reuse Environments with identical trim_blocks setting to improve performance +# reuse Environments with identical settings to improve performance @functools.lru_cache(maxsize=2) -def _get_environment(trim_blocks): +def _get_environment(): env = Environment( # Don't check if template files were modified upon re-rendering auto_reload=False, # Cache up to this number of templates for quick re-rendering cache_size=100, loader=FileSystemLoader(directories["templates"]), - trim_blocks=trim_blocks, + trim_blocks=True, ) env.filters.update(_FILTERS) return env @@ -62,12 +63,11 @@ def register_filter(name, func=None): return func -def render_to_string(template, content, trim_blocks=False, formater=None): +def render_to_string(template, content, formater=None): """Render a template from the template directory, raise on any errors. :param template: the path to the template relative to the template folder :param content: the dictionary of variables to put into rendering context - :param trim_blocks: controls the trim_blocks jinja2 feature :param formater: if given, it has to be a callable the rendered string is passed through @@ -78,7 +78,7 @@ def render_to_string(template, content, trim_blocks=False, formater=None): package is build (recovering the load time and overhead caused by having the file out of the code). """ - template = _get_environment(bool(trim_blocks)).get_template(template) + template = _get_environment().get_template(template) rendered = template.render(content) if formater is not None: rendered = formater(rendered) @@ -89,7 +89,6 @@ def render( destination, template, content, - trim_blocks=False, formater=None, permission=None, user=None, @@ -110,7 +109,7 @@ def render( # As we are opening the file with 'w', we are performing the rendering before # calling open() to not accidentally erase the file if rendering fails - rendered = render_to_string(template, content, trim_blocks, formater) + rendered = render_to_string(template, content, formater) # Write to file with open(destination, "w") as file: @@ -154,7 +153,7 @@ def is_ipv4(text): try: return ip_interface(text).version == 4 except: return False -@register_filter('ipv6') +@register_filter('is_ipv6') def is_ipv6(text): """ Filter IP address, return True on IPv6 address, False otherwise """ from ipaddress import ip_interface diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py index 7831af4d2..f556c5ced 100644 --- a/python/vyos/xml/definition.py +++ b/python/vyos/xml/definition.py @@ -255,7 +255,7 @@ class XML(dict): if not flat: # _flatten will make this conversion - d = self.multi_to_list(lpath, d) + d = self.multi_to_list(lpath, d, defaults=True) r = {} for k in d: @@ -284,7 +284,7 @@ class XML(dict): return _flatten(lpath, len(lpath), d) - def multi_to_list(self, lpath, conf): + def multi_to_list(self, lpath, conf, defaults=False): r = {} for k in conf: # key mangling could also be done here @@ -293,11 +293,14 @@ class XML(dict): under = k fpath = lpath + [k] if isinstance(conf[k],dict): - r[under] = self.multi_to_list(fpath, conf[k]) + r[under] = self.multi_to_list(fpath, conf[k], defaults) continue value = conf[k] if self.is_multi(fpath) and not isinstance(value, list): - value = value.split(' ') + if not defaults: + value = [value] + else: + value = value.split(' ') r[under] = value return r diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py index 41e48c2f8..e636e107d 100755 --- a/smoketest/scripts/cli/test_interfaces_openvpn.py +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -131,6 +131,12 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.session.set(path + ['tls', 'cert-file', ssl_cert]) self.session.set(path + ['tls', 'key-file', ssl_key]) + # check validate() - can not have auth username without a password + self.session.set(path + ['authentication', 'username', 'vyos']) + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(path + ['authentication', 'password', 'vyos']) + # client commit must pass self.session.commit() @@ -162,6 +168,8 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.session.set(path + ['tls', 'cert-file', ssl_cert]) self.session.set(path + ['tls', 'key-file', ssl_key]) self.session.set(path + ['vrf', vrf_name]) + self.session.set(path + ['authentication', 'username', interface+'user']) + self.session.set(path + ['authentication', 'password', interface+'secretpw']) self.session.commit() @@ -169,6 +177,7 @@ class TestInterfacesOpenVPN(unittest.TestCase): interface = f'vtun{ii}' remote_host = f'192.0.2.{ii}' config_file = f'/run/openvpn/{interface}.conf' + pw_file = f'/run/openvpn/{interface}.pw' config = read_file(config_file) self.assertIn(f'dev {interface}', config) @@ -180,6 +189,7 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.assertIn(f'persist-tun', config) self.assertIn(f'auth {auth_hash}', config) self.assertIn(f'cipher aes-256-cbc', config) + # TLS options self.assertIn(f'ca {ca_cert}', config) self.assertIn(f'cert {ssl_cert}', config) @@ -189,6 +199,10 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.assertEqual(get_vrf(interface), vrf_name) self.assertIn(interface, interfaces()) + pw = cmd(f'sudo cat {pw_file}') + self.assertIn(f'{interface}user', pw) + self.assertIn(f'{interface}secretpw', pw) + # check that no interface remained after deleting them self.session.delete(base_path) self.session.commit() @@ -330,6 +344,8 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.session.set(path + ['local-port', port]) self.session.set(path + ['server', 'subnet', subnet]) self.session.set(path + ['server', 'topology', 'subnet']) + self.session.set(path + ['keep-alive', 'failure-count', '5']) + self.session.set(path + ['keep-alive', 'interval', '5']) # clients self.session.set(path + ['server', 'client', 'client1', 'ip', client_ip]) @@ -370,6 +386,7 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.assertIn(f'topology subnet', config) self.assertIn(f'lport {port}', config) self.assertIn(f'push "redirect-gateway def1"', config) + self.assertIn(f'keepalive 5 25', config) # TLS options self.assertIn(f'ca {ca_cert}', config) @@ -423,6 +440,8 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.session.set(path + ['server', 'subnet', subnet]) self.session.set(path + ['server', 'topology', 'net30']) self.session.set(path + ['replace-default-route']) + self.session.set(path + ['keep-alive', 'failure-count', '10']) + self.session.set(path + ['keep-alive', 'interval', '5']) self.session.set(path + ['tls', 'ca-cert-file', ca_cert]) self.session.set(path + ['tls', 'cert-file', ssl_cert]) self.session.set(path + ['tls', 'key-file', ssl_key]) @@ -450,6 +469,7 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.assertIn(f'topology net30', config) self.assertIn(f'lport {port}', config) self.assertIn(f'push "redirect-gateway def1"', config) + self.assertIn(f'keepalive 5 50', config) # TLS options self.assertIn(f'ca {ca_cert}', config) @@ -534,7 +554,7 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.session.commit() - def test_site2site_interfaces(self): + def test_site2site_interfaces_tun(self): """ Create two OpenVPN site-to-site interfaces """ @@ -546,13 +566,22 @@ class TestInterfacesOpenVPN(unittest.TestCase): for ii in num_range: interface = f'vtun{ii}' local_address = f'192.0.{ii}.1' + local_address_subnet = '255.255.255.252' remote_address = f'172.16.{ii}.1' path = base_path + [interface] port = str(3000 + ii) + self.session.set(path + ['local-address', local_address]) + + # even numbers use tun type, odd numbers use tap type + if ii % 2 == 0: + self.session.set(path + ['device-type', 'tun']) + else: + self.session.set(path + ['device-type', 'tap']) + self.session.set(path + ['local-address', local_address, 'subnet-mask', local_address_subnet]) + self.session.set(path + ['mode', 'site-to-site']) self.session.set(path + ['local-port', port]) - self.session.set(path + ['local-address', local_address]) self.session.set(path + ['remote-port', port]) self.session.set(path + ['shared-secret-key-file', s2s_key]) self.session.set(path + ['remote-address', remote_address]) @@ -569,12 +598,19 @@ class TestInterfacesOpenVPN(unittest.TestCase): config_file = f'/run/openvpn/{interface}.conf' config = read_file(config_file) + # even numbers use tun type, odd numbers use tap type + if ii % 2 == 0: + self.assertIn(f'dev-type tun', config) + self.assertIn(f'ifconfig {local_address} {remote_address}', config) + else: + self.assertIn(f'dev-type tap', config) + self.assertIn(f'ifconfig {local_address} {local_address_subnet}', config) + self.assertIn(f'dev {interface}', config) - self.assertIn(f'dev-type tun', config) self.assertIn(f'secret {s2s_key}', config) self.assertIn(f'lport {port}', config) self.assertIn(f'rport {port}', config) - self.assertIn(f'ifconfig {local_address} {remote_address}', config) + self.assertTrue(process_named_running(PROCESS_NAME)) self.assertEqual(get_vrf(interface), vrf_name) diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index 4817321cf..aaff92dea 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -168,12 +168,12 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6]) - # Encapsulation mode requires IPv6 local-ip + # Encapsulation mode requires IPv4 local-ip with self.assertRaises(ConfigSessionError): self.session.commit() self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) - # Encapsulation mode requires IPv6 local-ip + # Encapsulation mode requires IPv4 local-ip with self.assertRaises(ConfigSessionError): self.session.commit() self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) @@ -360,7 +360,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): # No assertion is raised for GRE remote-ip when missing self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) - # Source interface can not be used with si + # Source interface can not be used with sit self.session.set(self._base_path + [interface, 'source-interface', source_if]) with self.assertRaises(ConfigSessionError): self.session.commit() diff --git a/smoketest/scripts/cli/test_policy_local-route.py b/smoketest/scripts/cli/test_policy_local-route.py new file mode 100755 index 000000000..490bf6b47 --- /dev/null +++ b/smoketest/scripts/cli/test_policy_local-route.py @@ -0,0 +1,61 @@ +#!/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 unittest + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.util import cmd +from vyos.util import process_named_running + +class PolicyLocalRouteTest(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + self._sources = ['203.0.113.1', '203.0.113.2'] + + def tearDown(self): + # Delete all policies + self.session.delete(['policy', 'local-route']) + self.session.commit() + del self.session + + # Test set table for some sources + def test_table_id(self): + base = ['policy', 'local-route'] + rule = '50' + table = '23' + for src in self._sources: + self.session.set(base + ['rule', rule, 'set', 'table', table]) + self.session.set(base + ['rule', rule, 'source', src]) + + self.session.commit() + + # Check generated configuration + + # Expected values + original = """ + 50: from 203.0.113.1 lookup 23 + 50: from 203.0.113.2 lookup 23 + """ + tmp = cmd('ip rule show prio 50') + original = original.split() + tmp = tmp.split() + + self.assertEqual(tmp, original) + +if __name__ == '__main__': + unittest.main() diff --git a/smoketest/scripts/cli/test_protocols_igmp-proxy.py b/smoketest/scripts/cli/test_protocols_igmp-proxy.py new file mode 100755 index 000000000..f78581fea --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_igmp-proxy.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-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 unittest + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.util import read_file +from vyos.util import process_named_running + +PROCESS_NAME = 'igmpproxy' +IGMP_PROXY_CONF = '/etc/igmpproxy.conf' +base_path = ['protocols', 'igmp-proxy'] +upstream_if = 'eth1' +downstream_if = 'eth2' + +class TestProtocolsIGMPProxy(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + self.session.set(['interfaces', 'ethernet', upstream_if, 'address', '172.16.1.1/24']) + + def tearDown(self): + self.session.delete(['interfaces', 'ethernet', upstream_if, 'address']) + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_igmpproxy(self): + threshold = '20' + altnet = '192.0.2.0/24' + whitelist = '10.0.0.0/8' + + self.session.set(base_path + ['disable-quickleave']) + self.session.set(base_path + ['interface', upstream_if, 'threshold', threshold]) + self.session.set(base_path + ['interface', upstream_if, 'alt-subnet', altnet]) + self.session.set(base_path + ['interface', upstream_if, 'whitelist', whitelist]) + + # Must define an upstream and at least 1 downstream interface! + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(base_path + ['interface', upstream_if, 'role', 'upstream']) + + # Interface does not exist + self.session.set(base_path + ['interface', 'eth20', 'role', 'upstream']) + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.delete(base_path + ['interface', 'eth20']) + + # Only 1 upstream interface allowed + self.session.set(base_path + ['interface', downstream_if, 'role', 'upstream']) + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(base_path + ['interface', downstream_if, 'role', 'downstream']) + + # commit changes + self.session.commit() + + # Check generated configuration + config = read_file(IGMP_PROXY_CONF) + self.assertIn(f'phyint {upstream_if} upstream ratelimit 0 threshold {threshold}', config) + self.assertIn(f'altnet {altnet}', config) + self.assertIn(f'whitelist {whitelist}', config) + self.assertIn(f'phyint {downstream_if} downstream ratelimit 0 threshold 1', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + +if __name__ == '__main__': + unittest.main() diff --git a/smoketest/scripts/cli/test_service_dhcp-relay.py b/smoketest/scripts/cli/test_service_dhcp-relay.py new file mode 100755 index 000000000..4b020db72 --- /dev/null +++ b/smoketest/scripts/cli/test_service_dhcp-relay.py @@ -0,0 +1,93 @@ +#!/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 re +import os +import unittest + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.util import cmd +from vyos.util import process_named_running +from vyos.util import read_file + +PROCESS_NAME = 'dhcrelay' +RELAY_CONF = '/run/dhcp-relay/dhcrelay.conf' +base_path = ['service', 'dhcp-relay'] + +class TestServiceDHCPRelay(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + + def tearDown(self): + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_relay_default(self): + max_size = '800' + hop_count = '20' + agents_packets = 'append' + servers = ['192.0.2.1', '192.0.2.2'] + + self.session.set(base_path + ['interface', 'lo']) + # check validate() - DHCP relay does not support the loopback interface + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.delete(base_path + ['interface', 'lo']) + + # activate DHCP relay on all ethernet interfaces + for tmp in Section.interfaces("ethernet"): + self.session.set(base_path + ['interface', tmp]) + + # check validate() - No DHCP relay server(s) configured + with self.assertRaises(ConfigSessionError): + self.session.commit() + for server in servers: + self.session.set(base_path + ['server', server]) + + self.session.set(base_path + ['relay-options', 'max-size', max_size]) + self.session.set(base_path + ['relay-options', 'hop-count', hop_count]) + self.session.set(base_path + ['relay-options', 'relay-agents-packets', agents_packets]) + + # commit changes + self.session.commit() + + # Check configured port + config = read_file(RELAY_CONF) + + # Test configured relay interfaces + for tmp in Section.interfaces("ethernet"): + self.assertIn(f'-i {tmp}', config) + + # Test relay servers + for server in servers: + self.assertIn(f' {server}', config) + + # Test max-size + self.assertIn(f'-A {max_size}', config) + # Hop count + self.assertIn(f'-c {hop_count}', config) + # relay-agents-packets + self.assertIn(f'-a -m {agents_packets}', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + +if __name__ == '__main__': + unittest.main() + diff --git a/smoketest/scripts/cli/test_service_dhcpv6-relay.py b/smoketest/scripts/cli/test_service_dhcpv6-relay.py new file mode 100755 index 000000000..ccc849a4f --- /dev/null +++ b/smoketest/scripts/cli/test_service_dhcpv6-relay.py @@ -0,0 +1,112 @@ +#!/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 re +import os +import unittest + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.template import address_from_cidr +from vyos.util import cmd +from vyos.util import process_named_running +from vyos.util import read_file + +PROCESS_NAME = 'dhcrelay' +RELAY_CONF = '/run/dhcp-relay/dhcrelay6.conf' +base_path = ['service', 'dhcpv6-relay'] + +upstream_if = 'eth0' +upstream_if_addr = '2001:db8::1/64' +listen_addr = '2001:db8:ffff::1/64' +interfaces = [] + +class TestServiceDHCPv6Relay(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + for tmp in interfaces: + listen = listen_addr + if tmp == upstream_if: + listen = upstream_if_addr + self.session.set(['interfaces', 'ethernet', tmp, 'address', listen]) + + def tearDown(self): + self.session.delete(base_path) + for tmp in interfaces: + listen = listen_addr + if tmp == upstream_if: + listen = upstream_if_addr + self.session.delete(['interfaces', 'ethernet', tmp, 'address', listen]) + + self.session.commit() + del self.session + + def test_relay_default(self): + dhcpv6_server = '2001:db8::ffff' + hop_count = '20' + + self.session.set(base_path + ['use-interface-id-option']) + self.session.set(base_path + ['max-hop-count', hop_count]) + + # check validate() - Must set at least one listen and upstream + # interface addresses. + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(base_path + ['upstream-interface', upstream_if, 'address', dhcpv6_server]) + + # check validate() - Must set at least one listen and upstream + # interface addresses. + with self.assertRaises(ConfigSessionError): + self.session.commit() + + # add listener on all ethernet interfaces except the upstream interface + for tmp in interfaces: + if tmp == upstream_if: + continue + self.session.set(base_path + ['listen-interface', tmp, 'address', listen_addr.split('/')[0]]) + + # commit changes + self.session.commit() + + # Check configured port + config = read_file(RELAY_CONF) + + # Test configured upstream interfaces + self.assertIn(f'-u {dhcpv6_server}%{upstream_if}', config) + + # Check listener on all ethernet interfaces + for tmp in interfaces: + if tmp == upstream_if: + continue + addr = listen_addr.split('/')[0] + self.assertIn(f'-l {addr}%{tmp}', config) + + # Check hop count + self.assertIn(f'-c {hop_count}', config) + # Check Interface ID option + self.assertIn('-I', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + +if __name__ == '__main__': + for tmp in Section.interfaces('ethernet'): + if '.' not in tmp: + interfaces.append(tmp) + + unittest.main() + diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py index f0c71e2de..ed006f16c 100755 --- a/smoketest/scripts/cli/test_service_pppoe-server.py +++ b/smoketest/scripts/cli/test_service_pppoe-server.py @@ -22,12 +22,9 @@ from base_accel_ppp_test import BasicAccelPPPTest from configparser import ConfigParser from vyos.configsession import ConfigSessionError from vyos.util import process_named_running -from vyos.util import cmd local_if = ['interfaces', 'dummy', 'dum667'] - ac_name = 'ACN' - interface = 'eth0' class TestServicePPPoEServer(BasicAccelPPPTest.BaseTest): diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py index 822a9aff2..e2744c936 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_system_ntp.py @@ -51,11 +51,15 @@ class TestSystemNTP(unittest.TestCase): """ Test basic NTP support with multiple servers and their options """ servers = ['192.0.2.1', '192.0.2.2'] options = ['noselect', 'preempt', 'prefer'] + ntp_pool = 'pool.vyos.io' for server in servers: for option in options: self.session.set(base_path + ['server', server, option]) + # Test NTP pool + self.session.set(base_path + ['server', ntp_pool, 'pool']) + # commit changes self.session.commit() @@ -65,6 +69,9 @@ class TestSystemNTP(unittest.TestCase): test = f'{server} iburst ' + ' '.join(options) self.assertTrue(test in tmp) + tmp = get_config_value('pool') + self.assertTrue(f'{ntp_pool} iburst' in tmp) + # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py index 78daeb6be..d93a2a8f4 100755 --- a/src/conf_mode/bcast_relay.py +++ b/src/conf_mode/bcast_relay.py @@ -79,7 +79,7 @@ def generate(relay): config['instance'] = instance render(config_file_base + instance, 'bcast-relay/udp-broadcast-relay.tmpl', - config, trim_blocks=True) + config) return None diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py index 352865b9d..6352e0b4a 100755 --- a/src/conf_mode/dhcp_relay.py +++ b/src/conf_mode/dhcp_relay.py @@ -19,81 +19,43 @@ import os from sys import exit from vyos.config import Config +from vyos.configdict import dict_merge from vyos.template import render from vyos.util import call +from vyos.util import dict_search +from vyos.xml import defaults from vyos import ConfigError - from vyos import airbag airbag.enable() -config_file = r'/run/dhcp-relay/dhcp.conf' - -default_config_data = { - 'interface': [], - 'server': [], - 'options': [], - 'hop_count': '10', - 'relay_agent_packets': 'forward' -} +config_file = r'/run/dhcp-relay/dhcrelay.conf' def get_config(config=None): - relay = default_config_data if config: conf = config else: conf = Config() - if not conf.exists(['service', 'dhcp-relay']): + base = ['service', 'dhcp-relay'] + if not conf.exists(base): return None - else: - conf.set_level(['service', 'dhcp-relay']) - - # Network interfaces to listen on - if conf.exists(['interface']): - relay['interface'] = conf.return_values(['interface']) - - # Servers equal to the address of the DHCP server(s) - if conf.exists(['server']): - relay['server'] = conf.return_values(['server']) - - conf.set_level(['service', 'dhcp-relay', 'relay-options']) - - if conf.exists(['hop-count']): - count = '-c ' + conf.return_value(['hop-count']) - relay['options'].append(count) - - # Specify the maximum packet size to send to a DHCPv4/BOOTP server. - # This might be done to allow sufficient space for addition of relay agent - # options while still fitting into the Ethernet MTU size. - # - # Available in DHCPv4 mode only: - if conf.exists(['max-size']): - size = '-A ' + conf.return_value(['max-size']) - relay['options'].append(size) - - # Control the handling of incoming DHCPv4 packets which already contain - # relay agent options. If such a packet does not have giaddr set in its - # header, the DHCP standard requires that the packet be discarded. However, - # if giaddr is set, the relay agent may handle the situation in four ways: - # It may append its own set of relay options to the packet, leaving the - # supplied option field intact; it may replace the existing agent option - # field; it may forward the packet unchanged; or, it may discard it. - # - # Available in DHCPv4 mode only: - if conf.exists(['relay-agents-packets']): - pkt = '-a -m ' + conf.return_value(['relay-agents-packets']) - relay['options'].append(pkt) + + relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # 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) + relay = dict_merge(default_values, relay) return relay def verify(relay): # bail out early - looks like removal from running config - if relay is None: + if not relay: return None - if 'lo' in relay['interface']: + if 'lo' in (dict_search('interface', relay) or []): raise ConfigError('DHCP relay does not support the loopback interface.') - if len(relay['server']) == 0: + if 'server' not in relay : raise ConfigError('No DHCP relay server(s) configured.\n' \ 'At least one DHCP relay server required.') @@ -104,17 +66,18 @@ def generate(relay): if not relay: return None - render(config_file, 'dhcp-relay/config.tmpl', relay) + render(config_file, 'dhcp-relay/dhcrelay.conf.tmpl', relay) return None def apply(relay): - if relay: - call('systemctl restart isc-dhcp-relay.service') - else: - # DHCP relay support is removed in the commit + # bail out early - looks like removal from running config + if not relay: call('systemctl stop isc-dhcp-relay.service') if os.path.exists(config_file): os.unlink(config_file) + return None + + call('systemctl restart isc-dhcp-relay.service') return None diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py index d4212b8be..cf8a26674 100755 --- a/src/conf_mode/dhcpv6_relay.py +++ b/src/conf_mode/dhcpv6_relay.py @@ -17,90 +17,84 @@ import os from sys import exit -from copy import deepcopy from vyos.config import Config -from vyos import ConfigError -from vyos.util import call +from vyos.configdict import dict_merge +from vyos.ifconfig import Interface from vyos.template import render - +from vyos.util import call +from vyos.util import dict_search +from vyos.validate import is_ipv6_link_local +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() -config_file = r'/run/dhcp-relay/dhcpv6.conf' - -default_config_data = { - 'listen_addr': [], - 'upstream_addr': [], - 'options': [], -} +config_file = '/run/dhcp-relay/dhcrelay6.conf' def get_config(config=None): - relay = deepcopy(default_config_data) if config: conf = config else: conf = Config() - if not conf.exists('service dhcpv6-relay'): + base = ['service', 'dhcpv6-relay'] + if not conf.exists(base): return None - else: - conf.set_level('service dhcpv6-relay') - - # Network interfaces/address to listen on for DHCPv6 query(s) - if conf.exists('listen-interface'): - interfaces = conf.list_nodes('listen-interface') - for intf in interfaces: - if conf.exists('listen-interface {0} address'.format(intf)): - addr = conf.return_value('listen-interface {0} address'.format(intf)) - listen = addr + '%' + intf - relay['listen_addr'].append(listen) - - # Upstream interface/address for remote DHCPv6 server - if conf.exists('upstream-interface'): - interfaces = conf.list_nodes('upstream-interface') - for intf in interfaces: - addresses = conf.return_values('upstream-interface {0} address'.format(intf)) - for addr in addresses: - server = addr + '%' + intf - relay['upstream_addr'].append(server) - - # Maximum hop count. When forwarding packets, dhcrelay discards packets - # which have reached a hop count of COUNT. Default is 10. Maximum is 255. - if conf.exists('max-hop-count'): - count = '-c ' + conf.return_value('max-hop-count') - relay['options'].append(count) - - if conf.exists('use-interface-id-option'): - relay['options'].append('-I') + + relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # 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) + relay = dict_merge(default_values, relay) return relay def verify(relay): # bail out early - looks like removal from running config - if relay is None: + if not relay: return None - if len(relay['listen_addr']) == 0 or len(relay['upstream_addr']) == 0: - raise ConfigError('Must set at least one listen and upstream interface addresses.') + if 'upstream_interface' not in relay: + raise ConfigError('At least one upstream interface required!') + for interface, config in relay['upstream_interface'].items(): + if 'address' not in config: + raise ConfigError('DHCPv6 server required for upstream ' \ + f'interface {interface}!') + + if 'listen_interface' not in relay: + raise ConfigError('At least one listen interface required!') + + # DHCPv6 relay requires at least one global unicat address assigned to the + # interface + for interface in relay['listen_interface']: + has_global = False + for addr in Interface(interface).get_addr(): + if not is_ipv6_link_local(addr.split('/')[0]): + has_global = True + if not has_global: + raise ConfigError(f'Interface {interface} does not have global '\ + 'IPv6 address assigned!') return None def generate(relay): # bail out early - looks like removal from running config - if relay is None: + if not relay: return None - render(config_file, 'dhcpv6-relay/config.tmpl', relay) + render(config_file, 'dhcp-relay/dhcrelay6.conf.tmpl', relay) return None def apply(relay): - if relay is not None: - call('systemctl restart isc-dhcp-relay6.service') - else: + # bail out early - looks like removal from running config + if not relay: # DHCPv6 relay support is removed in the commit call('systemctl stop isc-dhcp-relay6.service') if os.path.exists(config_file): os.unlink(config_file) + return None + + call('systemctl restart isc-dhcp-relay6.service') return None diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index ef52cbfd3..c44e6c974 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -114,10 +114,10 @@ def generate(dns): return None render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl', - dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group) + dns, user=pdns_rec_user, group=pdns_rec_group) render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl', - dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group) + dns, user=pdns_rec_user, group=pdns_rec_group) # if vyos-hostsd didn't create its files yet, create them (empty) for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index 93e995b78..6d39c6644 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -131,7 +131,9 @@ def generate(dyndns): if not dyndns: return None - render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns, trim_blocks=True, permission=0o600) + render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns, + permission=0o600) + return None def apply(dyndns): diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index de228f0f8..a6e2d9c8c 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -159,7 +159,7 @@ def generate(https): if 'server_block_list' not in https or not https['server_block_list']: https['server_block_list'] = [default_server_block] - render(config_file, 'https/nginx.default.tmpl', https, trim_blocks=True) + render(config_file, 'https/nginx.default.tmpl', https) return None diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py index 754f46566..fb030c9f3 100755 --- a/src/conf_mode/igmp_proxy.py +++ b/src/conf_mode/igmp_proxy.py @@ -17,90 +17,65 @@ import os from sys import exit -from copy import deepcopy - from netifaces import interfaces + from vyos.config import Config -from vyos import ConfigError -from vyos.util import call +from vyos.configdict import dict_merge from vyos.template import render - +from vyos.util import call +from vyos.util import dict_search +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() config_file = r'/etc/igmpproxy.conf' -default_config_data = { - 'disable': False, - 'disable_quickleave': False, - 'interfaces': [], -} - def get_config(config=None): - igmp_proxy = deepcopy(default_config_data) if config: conf = config else: conf = Config() - base = ['protocols', 'igmp-proxy'] - if not conf.exists(base): - return None - else: - conf.set_level(base) - - # Network interfaces to listen on - if conf.exists(['disable']): - igmp_proxy['disable'] = True - - # Option to disable "quickleave" - if conf.exists(['disable-quickleave']): - igmp_proxy['disable_quickleave'] = True - for intf in conf.list_nodes(['interface']): - conf.set_level(base + ['interface', intf]) - interface = { - 'name': intf, - 'alt_subnet': [], - 'role': 'downstream', - 'threshold': '1', - 'whitelist': [] - } - - if conf.exists(['alt-subnet']): - interface['alt_subnet'] = conf.return_values(['alt-subnet']) + base = ['protocols', 'igmp-proxy'] + igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - if conf.exists(['role']): - interface['role'] = conf.return_value(['role']) + if 'interface' in igmp_proxy: + # T2665: we must add the tagNode defaults individually until this is + # moved to the base class + default_values = defaults(base + ['interface']) + for interface in igmp_proxy['interface']: + igmp_proxy['interface'][interface] = dict_merge(default_values, + igmp_proxy['interface'][interface]) - if conf.exists(['threshold']): - interface['threshold'] = conf.return_value(['threshold']) - if conf.exists(['whitelist']): - interface['whitelist'] = conf.return_values(['whitelist']) + if conf.exists(['protocols', 'igmp']): + igmp_proxy.update({'igmp_configured': ''}) - # Append interface configuration to global configuration list - igmp_proxy['interfaces'].append(interface) + if conf.exists(['protocols', 'pim']): + igmp_proxy.update({'pim_configured': ''}) return igmp_proxy def verify(igmp_proxy): # bail out early - looks like removal from running config - if igmp_proxy is None: + if not igmp_proxy or 'disable' in igmp_proxy: return None - # bail out early - service is disabled - if igmp_proxy['disable']: - return None + if 'igmp_configured' in igmp_proxy or 'pim_configured' in igmp_proxy: + raise ConfigError('Can not configure both IGMP proxy and PIM '\ + 'at the same time') # at least two interfaces are required, one upstream and one downstream - if len(igmp_proxy['interfaces']) < 2: - raise ConfigError('Must define an upstream and at least 1 downstream interface!') + if 'interface' not in igmp_proxy or len(igmp_proxy['interface']) < 2: + raise ConfigError('Must define exactly one upstream and at least one ' \ + 'downstream interface!') upstream = 0 - for interface in igmp_proxy['interfaces']: - if interface['name'] not in interfaces(): - raise ConfigError('Interface "{}" does not exist'.format(interface['name'])) - if "upstream" == interface['role']: + for interface, config in igmp_proxy['interface'].items(): + if interface not in interfaces(): + raise ConfigError(f'Interface "{interface}" does not exist') + if dict_search('role', config) == 'upstream': upstream += 1 if upstream == 0: @@ -112,19 +87,20 @@ def verify(igmp_proxy): def generate(igmp_proxy): # bail out early - looks like removal from running config - if igmp_proxy is None: + if not igmp_proxy: return None # bail out early - service is disabled, but inform user - if igmp_proxy['disable']: - print('Warning: IGMP Proxy will be deactivated because it is disabled') + if 'disable' in igmp_proxy: + print('WARNING: IGMP Proxy will be deactivated because it is disabled') return None render(config_file, 'igmp-proxy/igmpproxy.conf.tmpl', igmp_proxy) + return None def apply(igmp_proxy): - if igmp_proxy is None or igmp_proxy['disable']: + if not igmp_proxy or 'disable' in igmp_proxy: # IGMP Proxy support is removed in the commit call('systemctl stop igmpproxy.service') if os.path.exists(config_file): diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 076bdb63e..7af3e3d7c 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -123,12 +123,12 @@ def get_config(config=None): # VLAN-aware bridge members must not have VLAN interface configuration if 'native_vlan' in interface_config: - if 'disable' not in interface_config['native_vlan']: - vlan_aware = True + vlan_aware = True if 'allowed_vlan' in interface_config: vlan_aware = True + if vlan_aware: tmp = has_vlan_subinterface_configured(conf,interface) if tmp: @@ -142,6 +142,8 @@ def verify(bridge): verify_dhcpv6(bridge) verify_vrf(bridge) + + vlan_aware = False if dict_search('member.interface', bridge): for interface, interface_config in bridge['member']['interface'].items(): @@ -168,6 +170,16 @@ def verify(bridge): if 'has_vlan' in interface_config: raise ConfigError(error_msg + 'it has an VLAN subinterface assigned!') + # VLAN-aware bridge members must not have VLAN interface configuration + if 'native_vlan' in interface_config: + vlan_aware = True + + if 'allowed_vlan' in interface_config: + vlan_aware = True + + if vlan_aware and 'wlan' in interface: + raise ConfigError(error_msg + 'VLAN aware cannot be set!') + if 'allowed_vlan' in interface_config: for vlan in interface_config['allowed_vlan']: if re.search('[0-9]{1,4}-[0-9]{1,4}', vlan): diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index c23e79948..25920f893 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -120,7 +120,7 @@ def verify(openvpn): # OpenVPN site-to-site - VERIFY # elif openvpn['mode'] == 'site-to-site': - if not 'local_address' in openvpn: + if 'local_address' not in openvpn and 'is_bridge_member' not in openvpn: raise ConfigError('Must specify "local-address" or add interface to bridge') if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1: @@ -166,15 +166,16 @@ def verify(openvpn): if dict_search('remote_host', openvpn) in dict_search('remote_address', openvpn): raise ConfigError('"remote-address" and "remote-host" can not be the same') - - if 'local_address' in openvpn: + if openvpn['device_type'] == 'tap': # we can only have one local_address, this is ensured above v4addr = None for laddr in openvpn['local_address']: - if is_ipv4(laddr): v4addr = laddr + if is_ipv4(laddr): + v4addr = laddr + break - if 'remote_address' not in openvpn and (v4addr not in openvpn['local_address'] or 'subnet_mask' not in openvpn['local_address'][v4addr]): - raise ConfigError('IPv4 "local-address" requires IPv4 "remote-address" or IPv4 "local-address subnet"') + if v4addr in openvpn['local_address'] and 'subnet_mask' not in openvpn['local_address'][v4addr]: + raise ConfigError('Must specify IPv4 "subnet-mask" for local-address') if dict_search('encryption.ncp_ciphers', openvpn): raise ConfigError('NCP ciphers can only be used in client or server mode') @@ -464,12 +465,9 @@ def generate(openvpn): if tmp: fix_permissions.append(tmp) # Generate User/Password authentication file - if 'auth' in openvpn: - with open(openvpn['auth_user_pass_file'], 'w') as f: - f.write('{}\n{}'.format(openvpn['auth_user'], openvpn['auth_pass'])) - # also change permission on auth file - fix_permissions.append(openvpn['auth_user_pass_file']) - + if 'authentication' in openvpn: + render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.tmpl', openvpn, + user=user, group=group, permission=0o600) else: # delete old auth file if present if os.path.isfile(openvpn['auth_user_pass_file']): @@ -483,17 +481,13 @@ def generate(openvpn): # Our client need's to know its subnet mask ... client_config['server_subnet'] = dict_search('server.subnet', openvpn) - import pprint - pprint.pprint(client_config) - render(client_file, 'openvpn/client.conf.tmpl', client_config, - trim_blocks=True, user=user, group=group) + user=user, group=group) # we need to support quoting of raw parameters from OpenVPN CLI # see https://phabricator.vyos.net/T1632 render(cfg_file.format(**openvpn), 'openvpn/server.conf.tmpl', openvpn, - trim_blocks=True, formater=lambda _: _.replace(""", '"'), - user=user, group=group) + formater=lambda _: _.replace(""", '"'), user=user, group=group) # Fixup file permissions for file in fix_permissions: diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index ee3b142c8..c31e49574 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -93,25 +93,25 @@ def generate(pppoe): return None # Create PPP configuration files - render(config_pppoe, 'pppoe/peer.tmpl', - pppoe, trim_blocks=True, permission=0o755) + render(config_pppoe, 'pppoe/peer.tmpl', pppoe, permission=0o755) + # Create script for ip-pre-up.d - render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl', - pppoe, trim_blocks=True, permission=0o755) + render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl', pppoe, + permission=0o755) # Create script for ip-up.d - render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl', - pppoe, trim_blocks=True, permission=0o755) + render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl', pppoe, + permission=0o755) # Create script for ip-down.d - render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl', - pppoe, trim_blocks=True, permission=0o755) + render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl', pppoe, + permission=0o755) # Create script for ipv6-up.d - render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', - pppoe, trim_blocks=True, permission=0o755) + render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', pppoe, + permission=0o755) if 'dhcpv6_options' in pppoe and 'pd' in pppoe['dhcpv6_options']: # ipv6.tmpl relies on ifname - this should be made consitent in the # future better then double key-ing the same value - render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe, trim_blocks=True) + render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe) return None diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index f1217b62d..1a7e9a96d 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -15,354 +15,124 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -import netifaces from sys import exit -from copy import deepcopy from netifaces import interfaces from vyos.config import Config -from vyos.configdict import is_member -from vyos.configdict import list_diff -from vyos.dicts import FixedDict -from vyos.ifconfig import Interface, GREIf, GRETapIf, IPIPIf, IP6GREIf, IPIP6If, IP6IP6If, SitIf, Sit6RDIf -from vyos.ifconfig.afi import IP4, IP6 +from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict +from vyos.configdict import node_changed +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_vrf +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_mtu_ipv6 +from vyos.ifconfig import Interface +from vyos.ifconfig import GREIf +from vyos.ifconfig import GRETapIf +from vyos.ifconfig import IPIPIf +from vyos.ifconfig import IP6GREIf +from vyos.ifconfig import IPIP6If +from vyos.ifconfig import IP6IP6If +from vyos.ifconfig import SitIf +from vyos.ifconfig import Sit6RDIf from vyos.template import is_ipv4 from vyos.template import is_ipv6 +from vyos.util import dict_search from vyos import ConfigError - - from vyos import airbag airbag.enable() - -class ConfigurationState(object): +def get_config(config=None): """ - The current API require a dict to be generated by get_config() - which is then consumed by verify(), generate() and apply() - - ConfiguartionState is an helper class wrapping Config and providing - an common API to this dictionary structure - - Its to_api() function return a dictionary containing three fields, - each a dict, called options, changes, actions. - - options: - - contains the configuration options for the dict and its value - {'options': {'commment': 'test'}} will be set if - 'set interface dummy dum1 description test' was used and - the key 'commment' is used to index the description info. - - changes: - - per key, let us know how the data was modified using one of the action - a special key called 'section' is used to indicate what happened to the - section. for example: - - 'set interface dummy dum1 description test' when no interface was setup - will result in the following changes - {'changes': {'section': 'create', 'comment': 'create'}} - - on an existing interface, depending if there was a description - 'set interface dummy dum1 description test' will result in one of - {'changes': {'comment': 'create'}} (not present before) - {'changes': {'comment': 'static'}} (unchanged) - {'changes': {'comment': 'modify'}} (changed from half) - - and 'delete interface dummy dummy1 description' will result in: - {'changes': {'comment': 'delete'}} - - actions: - - for each action list the configuration key which were changes - in our example if we added the 'description' and added an IP we would have - {'actions': { 'create': ['comment'], 'modify': ['addresses-add']}} - - the actions are: - 'create': it did not exist previously and was created - 'modify': it did exist previously but its content changed - 'static': it did exist and did not change - 'delete': it was present but was removed from the configuration - 'absent': it was not and is not present - which for each field represent how it was modified since the last commit + Retrive CLI config as dictionary. Dictionary can never be empty, as at least + the interface name will be added or a deleted flag """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'tunnel'] + tunnel = get_interface_dict(conf, base) - def __init__(self, configuration, section, default): - """ - initialise the class for a given configuration path: - - >>> conf = ConfigurationState(conf, 'interfaces ethernet eth1') - all further references to get_value(s) and get_effective(s) - will be for this part of the configuration (eth1) - """ - self._conf = configuration - - self.default = deepcopy(default) - self.options = FixedDict(**default) - self.actions = { - 'create': [], # the key did not exist and was added - 'static': [], # the key exists and its value was not modfied - 'modify': [], # the key exists and its value was modified - 'absent': [], # the key is not present - 'delete': [], # the key was present and was deleted - } - self.changes = {} - if not self._conf.exists(section): - self.changes['section'] = 'delete' - elif self._conf.exists_effective(section): - self.changes['section'] = 'modify' - else: - self.changes['section'] = 'create' - - self.set_level(section) - - def set_level(self, lpath): - self.section = lpath - self._conf.set_level(lpath) - - def _act(self, section): - """ - Returns for a given configuration field determine what happened to it - - 'create': it did not exist previously and was created - 'modify': it did exist previously but its content changed - 'static': it did exist and did not change - 'delete': it was present but was removed from the configuration - 'absent': it was not and is not present - """ - if self._conf.exists(section): - if self._conf.exists_effective(section): - if self._conf.return_value(section) != self._conf.return_effective_value(section): - return 'modify' - return 'static' - return 'create' - else: - if self._conf.exists_effective(section): - return 'delete' - return 'absent' - - def _action(self, name, key): - action = self._act(key) - self.changes[name] = action - self.actions[action].append(name) - return action - - def _get(self, name, key, default, getter): - value = getter(key) - if not value: - if default: - self.options[name] = default - return - self.options[name] = self.default[name] - return - self.options[name] = value - - def get_value(self, name, key, default=None): - """ - >>> conf.get_value('comment', 'description') - will place the string of 'interface dummy description test' - into the dictionnary entry 'comment' using Config.return_value - (the data in the configuration to apply) - """ - if self._action(name, key) in ('delete', 'absent'): - return - return self._get(name, key, default, self._conf.return_value) - - def get_values(self, name, key, default=None): - """ - >>> conf.get_values('addresses', 'address') - will place a list of the new IP present in 'interface dummy dum1 address' - into the dictionnary entry "-add" (here 'addresses-add') using - Config.return_values and will add the the one which were removed in into - the entry "-del" (here addresses-del') - """ - add_name = f'{name}-add' - - if self._action(add_name, key) in ('delete', 'absent'): - return - - self._get(add_name, key, default, self._conf.return_values) - - # get the effective values to determine which data is no longer valid - self.options['addresses-del'] = list_diff( - self._conf.return_effective_values('address'), - self.options['addresses-add'] - ) - - def get_effective(self, name, key, default=None): - """ - >>> conf.get_value('comment', 'description') - will place the string of 'interface dummy description test' - into the dictionnary entry 'comment' using Config.return_effective_value - (the data in the configuration to apply) - """ - self._action(name, key) - return self._get(name, key, default, self._conf.return_effective_value) - - def get_effectives(self, name, key, default=None): - """ - >>> conf.get_effectives('addresses-add', 'address') - will place a list made of the IP present in 'interface ethernet eth1 address' - into the dictionnary entry 'addresses-add' using Config.return_effectives_value - (the data in the un-modified configuration) - """ - self._action(name, key) - return self._get(name, key, default, self._conf.return_effectives_value) + # Wireguard is "special" the default MTU is 1420 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + tunnel['mtu'] = '1476' - def load(self, mapping): - """ - load will take a dictionary defining how we wish the configuration - to be parsed and apply this definition to set the data. + tmp = leaf_node_changed(conf, ['encapsulation']) + if tmp: tunnel.update({'encapsulation_changed': {}}) - >>> mapping = { - 'addresses-add' : ('address', True, None), - 'comment' : ('description', False, 'auto'), - } - >>> conf.load(mapping) + # We must check if our interface is configured to be a DMVPN member + nhrp_base = ['protocols', 'nhrp', 'tunnel'] + conf.set_level(nhrp_base) + nhrp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if nhrp: tunnel.update({'nhrp' : list(nhrp.keys())}) - mapping is a dictionary where each key represents the name we wish - to have (such as 'addresses-add'), with a list a content representing - how the data should be parsed: - - the configuration section name - such as 'address' under 'interface ethernet eth1' - - boolean indicating if this data can have multiple values - for 'address', True, as multiple IPs can be set - for 'description', False, as it is a single string - - default represent the default value if absent from the configuration - 'None' indicate that no default should be set if the configuration - does not have the configuration section + return tunnel - """ - for local_name, (config_name, multiple, default) in mapping.items(): - if multiple: - self.get_values(local_name, config_name, default) - else: - self.get_value(local_name, config_name, default) +def verify(tunnel): + if 'deleted' in tunnel: + verify_bridge_delete(tunnel) - def remove_default(self,*options): - """ - remove all the values which were not changed from the default - """ - for option in options: - if not self._conf.exists(option): - del self.options[option] - continue + if 'nhrp' in tunnel and tunnel['ifname'] in tunnel['nhrp']: + raise ConfigError('Tunnel used for NHRP, it can not be deleted!') - if self._conf.return_value(option) == self.default[option]: - del self.options[option] - continue + return None - if self._conf.return_values(option) == self.default[option]: - del self.options[option] - continue + if 'encapsulation' not in tunnel: + raise ConfigError('Must configure the tunnel encapsulation for '\ + '{ifname}!'.format(**tunnel)) - def as_dict(self, lpath): - l = self._conf.get_level() - self._conf.set_level([]) - d = self._conf.get_config_dict(lpath) - # XXX: that not what I would have expected from get_config_dict - if lpath: - d = d[lpath[-1]] - # XXX: it should have provided me the content and not the key - self._conf.set_level(l) - return d + verify_mtu_ipv6(tunnel) + verify_address(tunnel) + verify_vrf(tunnel) - def to_api(self): - """ - provide a dictionary with the generated data for the configuration - options: the configuration value for the key - changes: per key how they changed from the previous configuration - actions: per changes all the options which were changed - """ - # as we have to use a dict() for the API for verify and apply the options - return { - 'options': self.options, - 'changes': self.changes, - 'actions': self.actions, - } + if 'local_ip' not in tunnel and 'dhcp_interface' not in tunnel: + raise ConfigError('local-ip is mandatory for tunnel') + if 'remote_ip' not in tunnel and tunnel['encapsulation'] != 'gre': + raise ConfigError('remote-ip is mandatory for tunnel') -default_config_data = { - # interface definition - 'vrf': '', - 'addresses-add': [], - 'addresses-del': [], - 'state': 'up', - 'dhcp-interface': '', - 'link_detect': 1, - 'ip': False, - 'ipv6': False, - 'nhrp': [], - 'arp_filter': 1, - 'arp_accept': 0, - 'arp_announce': 0, - 'arp_ignore': 0, - 'ipv6_accept_ra': 1, - 'ipv6_autoconf': 0, - 'ipv6_forwarding': 1, - 'ipv6_dad_transmits': 1, - # internal - 'interfaces': [], - 'tunnel': {}, - 'bridge': '', - # the following names are exactly matching the name - # for the ip command and must not be changed - 'ifname': '', - 'type': '', - 'alias': '', - 'mtu': '1476', - 'local': '', - 'remote': '', - 'dev': '', - 'multicast': 'disable', - 'allmulticast': 'disable', - 'ttl': '255', - 'tos': 'inherit', - 'key': '', - 'encaplimit': '4', - 'flowlabel': 'inherit', - 'hoplimit': '64', - 'tclass': 'inherit', - '6rd-prefix': '', - '6rd-relay-prefix': '', -} + if {'local_ip', 'dhcp_interface'} <= set(tunnel): + raise ConfigError('Can not use both local-ip and dhcp-interface') + if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']: + error_ipv6 = 'Encapsulation mode requires IPv6' + if 'local_ip' in tunnel and not is_ipv6(tunnel['local_ip']): + raise ConfigError(f'{error_ipv6} local-ip') -# dict name -> config name, multiple values, default -mapping = { - 'type': ('encapsulation', False, None), - 'alias': ('description', False, None), - 'mtu': ('mtu', False, None), - 'local': ('local-ip', False, None), - 'remote': ('remote-ip', False, None), - 'multicast': ('multicast', False, None), - 'dev': ('source-interface', False, None), - 'ttl': ('parameters ip ttl', False, None), - 'tos': ('parameters ip tos', False, None), - 'key': ('parameters ip key', False, None), - 'encaplimit': ('parameters ipv6 encaplimit', False, None), - 'flowlabel': ('parameters ipv6 flowlabel', False, None), - 'hoplimit': ('parameters ipv6 hoplimit', False, None), - 'tclass': ('parameters ipv6 tclass', False, None), - '6rd-prefix': ('6rd-prefix', False, None), - '6rd-relay-prefix': ('6rd-relay-prefix', False, None), - 'dhcp-interface': ('dhcp-interface', False, None), - 'state': ('disable', False, 'down'), - 'link_detect': ('disable-link-detect', False, 2), - 'vrf': ('vrf', False, None), - 'addresses': ('address', True, None), - 'arp_filter': ('ip disable-arp-filter', False, 0), - 'arp_accept': ('ip enable-arp-accept', False, 1), - 'arp_announce': ('ip enable-arp-announce', False, 1), - 'arp_ignore': ('ip enable-arp-ignore', False, 1), - 'ipv6_autoconf': ('ipv6 address autoconf', False, 1), - 'ipv6_forwarding': ('ipv6 disable-forwarding', False, 0), - 'ipv6_dad_transmits:': ('ipv6 dup-addr-detect-transmits', False, None) -} + if 'remote_ip' in tunnel and not is_ipv6(tunnel['remote_ip']): + raise ConfigError(f'{error_ipv6} remote-ip') + else: + error_ipv4 = 'Encapsulation mode requires IPv4' + if 'local_ip' in tunnel and not is_ipv4(tunnel['local_ip']): + raise ConfigError(f'{error_ipv4} local-ip') + + if 'remote_ip' in tunnel and not is_ipv4(tunnel['remote_ip']): + raise ConfigError(f'{error_ipv4} remote-ip') + + if tunnel['encapsulation'] in ['sit', 'gre-bridge']: + if 'source_interface' in tunnel: + raise ConfigError('Option source-interface can not be used with ' \ + 'encapsulation "sit" or "gre-bridge"') + elif tunnel['encapsulation'] == 'gre': + if 'local_ip' in tunnel and is_ipv6(tunnel['local_ip']): + raise ConfigError('Can not use local IPv6 address is for mGRE tunnels') + +def generate(tunnel): + return None +def apply(tunnel): + if 'deleted' in tunnel or 'encapsulation_changed' in tunnel: + if tunnel['ifname'] in interfaces(): + tmp = Interface(tunnel['ifname']) + tmp.remove() + if 'deleted' in tunnel: + return None -def get_class (options): dispatch = { 'gre': GREIf, 'gre-bridge': GRETapIf, @@ -373,353 +143,52 @@ def get_class (options): 'sit': SitIf, } - kls = dispatch[options['type']] - if options['type'] == 'gre' and not options['remote'] \ - and not options['key'] and not options['multicast']: - # will use GreTapIf on GreIf deletion but it does not matter - return GRETapIf - elif options['type'] == 'sit' and options['6rd-prefix']: - # will use SitIf on Sit6RDIf deletion but it does not matter - return Sit6RDIf - return kls - -def get_interface_ip (ifname): - if not ifname: - return '' - try: - addrs = Interface(ifname).get_addr() - if addrs: - return addrs[0].split('/')[0] - except Exception: - return '' - -def get_afi (ip): - return IP6 if is_ipv6(ip) else IP4 - -def ip_proto (afi): - return 6 if afi == IP6 else 4 - - -def get_config(config=None): - ifname = os.environ.get('VYOS_TAGNODE_VALUE','') - if not ifname: - raise ConfigError('Interface not specified') - - if config: - config = config - else: - config = Config() - - conf = ConfigurationState(config, ['interfaces', 'tunnel ', ifname], default_config_data) - options = conf.options - changes = conf.changes - options['ifname'] = ifname - - if changes['section'] == 'delete': - conf.get_effective('type', mapping['type'][0]) - config.set_level(['protocols', 'nhrp', 'tunnel']) - options['nhrp'] = config.list_nodes('') - return conf.to_api() - - # load all the configuration option according to the mapping - conf.load(mapping) - - # remove default value if not set and not required - afi_local = get_afi(options['local']) - if afi_local == IP6: - conf.remove_default('ttl', 'tos', 'key') - if afi_local == IP4: - conf.remove_default('encaplimit', 'flowlabel', 'hoplimit', 'tclass') - - # if the local-ip is not set, pick one from the interface ! - # hopefully there is only one, otherwise it will not be very deterministic - # at time of writing the code currently returns ipv4 before ipv6 in the list - - # XXX: There is no way to trigger an update of the interface source IP if - # XXX: the underlying interface IP address does change, I believe this - # XXX: limit/issue is present in vyatta too - - if not options['local'] and options['dhcp-interface']: - # XXX: This behaviour changes from vyatta which would return 127.0.0.1 if - # XXX: the interface was not DHCP. As there is no easy way to find if an - # XXX: interface is using DHCP, and using this feature to get 127.0.0.1 - # XXX: makes little sense, I feel the change in behaviour is acceptable - picked = get_interface_ip(options['dhcp-interface']) - if picked == '': - picked = '127.0.0.1' - print('Could not get an IP address from {dhcp-interface} using 127.0.0.1 instead') - options['local'] = picked - options['dhcp-interface'] = '' - - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if options['ipv6_autoconf'] or 'dhcpv6' in options['addresses-add']: - options['ipv6_accept_ra'] = 2 - - # allmulticast fate is linked to multicast - options['allmulticast'] = options['multicast'] - - # check that per encapsulation all local-remote pairs are unique - ct = conf.as_dict(['interfaces', 'tunnel']) - options['tunnel'] = {} - - # check for bridges - tmp = is_member(config, ifname, 'bridge') - if tmp: options['bridge'] = next(iter(tmp)) - options['interfaces'] = interfaces() - - for name in ct: - tunnel = ct[name] - encap = tunnel.get('encapsulation', '') - local = tunnel.get('local-ip', '') - if not local: - local = get_interface_ip(tunnel.get('dhcp-interface', '')) - remote = tunnel.get('remote-ip', '<unset>') - pair = f'{local}-{remote}' - options['tunnel'][encap][pair] = options['tunnel'].setdefault(encap, {}).get(pair, 0) + 1 - - return conf.to_api() - - -def verify(conf): - options = conf['options'] - changes = conf['changes'] - actions = conf['actions'] - - ifname = options['ifname'] - iftype = options['type'] - - if changes['section'] == 'delete': - if ifname in options['nhrp']: - raise ConfigError(( - f'Cannot delete interface tunnel {iftype} {ifname}, ' - 'it is used by NHRP')) - - if options['bridge']: - raise ConfigError(( - f'Cannot delete interface "{options["ifname"]}" as it is a ' - f'member of bridge "{options["bridge"]}"!')) - - # done, bail out early - return None - - # tunnel encapsulation checks - - if not iftype: - raise ConfigError(f'Must provide an "encapsulation" for tunnel {iftype} {ifname}') - - if changes['type'] in ('modify', 'delete'): - # TODO: we could now deal with encapsulation modification by deleting / recreating - raise ConfigError(f'Encapsulation can only be set at tunnel creation for tunnel {iftype} {ifname}') - - if iftype != 'sit' and options['6rd-prefix']: - # XXX: should be able to remove this and let the definition catch it - print(f'6RD can only be configured for sit interfaces not tunnel {iftype} {ifname}') - - # what are the tunnel options we can set / modified / deleted - - kls = get_class(options) - valid = kls.updates + ['alias', 'addresses-add', 'addresses-del', 'vrf', 'state'] - valid += ['arp_filter', 'arp_accept', 'arp_announce', 'arp_ignore'] - valid += ['ipv6_accept_ra', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'] - - if changes['section'] == 'create': - valid.extend(['type',]) - valid.extend([o for o in kls.options if o not in kls.updates]) - - for create in actions['create']: - if create not in valid: - raise ConfigError(f'Can not set "{create}" for tunnel {iftype} {ifname} at tunnel creation') - - for modify in actions['modify']: - if modify not in valid: - raise ConfigError(f'Can not modify "{modify}" for tunnel {iftype} {ifname}. it must be set at tunnel creation') - - for delete in actions['delete']: - if delete in kls.required: - raise ConfigError(f'Can not remove "{delete}", it is an mandatory option for tunnel {iftype} {ifname}') - - # tunnel information - - tun_local = options['local'] - afi_local = get_afi(tun_local) - tun_remote = options['remote'] or tun_local - afi_remote = get_afi(tun_remote) - tun_ismgre = iftype == 'gre' and not options['remote'] - tun_is6rd = iftype == 'sit' and options['6rd-prefix'] - tun_dev = options['dev'] - - # incompatible options - - if not tun_local and not options['dhcp-interface'] and not tun_is6rd: - raise ConfigError(f'Must configure either local-ip or dhcp-interface for tunnel {iftype} {ifname}') - - if tun_local and options['dhcp-interface']: - raise ConfigError(f'Must configure only one of local-ip or dhcp-interface for tunnel {iftype} {ifname}') - - if tun_dev and iftype in ('gre-bridge', 'sit'): - raise ConfigError(f'source interface can not be used with {iftype} {ifname}') - - # tunnel endpoint - - if afi_local != afi_remote: - raise ConfigError(f'IPv4/IPv6 mismatch between local-ip and remote-ip for tunnel {iftype} {ifname}') - - if afi_local != kls.tunnel: - version = 4 if tun_local == IP4 else 6 - raise ConfigError(f'Invalid IPv{version} local-ip for tunnel {iftype} {ifname}') - - ipv4_count = len([ip for ip in options['addresses-add'] if is_ipv4(ip)]) - ipv6_count = len([ip for ip in options['addresses-add'] if is_ipv6(ip)]) - - if tun_ismgre and afi_local == IP6: - raise ConfigError(f'Using an IPv6 address is forbidden for mGRE tunnels such as tunnel {iftype} {ifname}') - - # check address family use - # checks are not enforced (but ip command failing) for backward compatibility - - if ipv4_count and not IP4 in kls.ip: - print(f'Should not use IPv4 addresses on tunnel {iftype} {ifname}') - - if ipv6_count and not IP6 in kls.ip: - print(f'Should not use IPv6 addresses on tunnel {iftype} {ifname}') - - # vrf check - if options['vrf']: - if options['vrf'] not in options['interfaces']: - raise ConfigError(f'VRF "{options["vrf"]}" does not exist') - - if options['bridge']: - raise ConfigError(( - f'Interface "{options["ifname"]}" cannot be member of VRF ' - f'"{options["vrf"]}" and bridge {options["bridge"]} ' - f'at the same time!')) - - # bridge and address check - if ( options['bridge'] - and ( options['addresses-add'] - or options['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{options["name"]}" ' - f'as it is a member of bridge "{options["bridge"]}"!')) - - # source-interface check - - if tun_dev and tun_dev not in options['interfaces']: - raise ConfigError(f'device "{tun_dev}" does not exist') - - # tunnel encapsulation check - - convert = { - (6, 4, 'gre'): 'ip6gre', - (6, 6, 'gre'): 'ip6gre', - (4, 6, 'ipip'): 'ipip6', - (6, 6, 'ipip'): 'ip6ip6', + # We need to re-map the tunnel encapsulation proto to a valid interface class + encap = tunnel['encapsulation'] + klass = dispatch[encap] + + # This is a special type of interface which needs additional parameters + # when created using iproute2. Instead of passing a ton of arguments, + # use a dictionary provided by the interface class which holds all the + # options necessary. + conf = klass.get_config() + + # Copy/re-assign our dictionary values to values understood by the + # derived _Tunnel classes + mapping = { + # this : get_config() + 'local_ip' : 'local', + 'remote_ip' : 'remote', + 'source_interface' : 'dev', + 'parameters.ip.ttl' : 'ttl', + 'parameters.ip.tos' : 'tos', + 'parameters.ip.key' : 'key', + 'parameters.ipv6.encaplimit' : 'encaplimit' } - iprotos = [] - if ipv4_count: - iprotos.append(4) - if ipv6_count: - iprotos.append(6) - - for iproto in iprotos: - replace = convert.get((kls.tunnel, iproto, iftype), '') - if replace: - raise ConfigError( - f'Using IPv6 address in local-ip or remote-ip is not possible with "encapsulation {iftype}". ' + - f'Use "encapsulation {replace}" for tunnel {iftype} {ifname} instead.' - ) - - # tunnel options - - incompatible = [] - if afi_local == IP6: - incompatible.extend(['ttl', 'tos', 'key',]) - if afi_local == IP4: - incompatible.extend(['encaplimit', 'flowlabel', 'hoplimit', 'tclass']) - - for option in incompatible: - if option in options: - # TODO: raise converted to print as not enforced by vyatta - # raise ConfigError(f'{option} is not valid for tunnel {iftype} {ifname}') - print(f'Using "{option}" is invalid for tunnel {iftype} {ifname}') - - # duplicate tunnel pairs - - pair = '{}-{}'.format(options['local'], options['remote']) - if options['tunnel'].get(iftype, {}).get(pair, 0) > 1: - raise ConfigError(f'More than one tunnel configured for with the same encapulation and IPs for tunnel {iftype} {ifname}') + # Add additional IPv6 options if tunnel is IPv6 aware + if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']: + mappingv6 = { + # this : get_config() + 'parameters.ipv6.encaplimit' : 'encaplimit' + } + mapping.update(mappingv6) - return None + for our_key, their_key in mapping.items(): + if dict_search(our_key, tunnel) and their_key in conf: + conf[their_key] = dict_search(our_key, tunnel) + tun = klass(tunnel['ifname'], **conf) + tun.change_options() + tun.update(tunnel) -def generate(gre): return None -def apply(conf): - options = conf['options'] - changes = conf['changes'] - actions = conf['actions'] - kls = get_class(options) - - # extract ifname as otherwise it is duplicated on the interface creation - ifname = options.pop('ifname') - - # only the valid keys for creation of a Interface - config = dict((k, options[k]) for k in kls.options if options[k]) - - # setup or create the tunnel interface if it does not exist - tunnel = kls(ifname, **config) - - if changes['section'] == 'delete': - tunnel.remove() - # The perl code was calling/opt/vyatta/sbin/vyatta-tunnel-cleanup - # which identified tunnels type which were not used anymore to remove them - # (ie: gre0, gretap0, etc.) The perl code did however nothing - # This feature is also not implemented yet - return - - # A GRE interface without remote will be mGRE - # if the interface does not suppor the option, it skips the change - for option in tunnel.updates: - if changes['section'] in 'create' and option in tunnel.options: - # it was setup at creation - continue - if not options[option]: - # remote can be set to '' and it would generate an invalide command - continue - tunnel.set_interface(option, options[option]) - - # set other interface properties - for option in ('alias', 'mtu', 'link_detect', 'multicast', 'allmulticast', - 'arp_accept', 'arp_filter', 'arp_announce', 'arp_ignore', - 'ipv6_accept_ra', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'): - if not options[option]: - # should never happen but better safe - continue - tunnel.set_interface(option, options[option]) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not options['bridge']: - tunnel.set_vrf(options['vrf']) - - # Configure interface address(es) - for addr in options['addresses-del']: - tunnel.del_addr(addr) - for addr in options['addresses-add']: - tunnel.add_addr(addr) - - # now bring it up (or not) - tunnel.set_admin_state(options['state']) - - if __name__ == '__main__': try: c = get_config() - verify(c) generate(c) + verify(c) apply(c) except ConfigError as e: print(e) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 9bda35d0a..7cfc76aa0 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -80,9 +80,6 @@ def verify(wireguard): raise ConfigError('Wireguard private-key not found! Execute: ' \ '"run generate wireguard [default-keypair|named-keypairs]"') - if 'address' not in wireguard: - raise ConfigError('IP address required!') - if 'peer' not in wireguard: raise ConfigError('At least one Wireguard peer is required!') diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 5d723bbfd..b25fcd4e0 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -241,10 +241,12 @@ def generate(wifi): # render appropriate new config files depending on access-point or station mode if wifi['type'] == 'access-point': - render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', wifi, trim_blocks=True) + render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', + wifi) elif wifi['type'] == 'station': - render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl', wifi, trim_blocks=True) + render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl', + wifi) return None @@ -261,7 +263,6 @@ def apply(wifi): # Assign WiFi instance configuration parameters to config dict conf['phy'] = wifi['physical_device'] - conf['wds'] = 'on' if 'wds' in wifi else 'off' # Finally create the new interface w = WiFiIf(interface, **conf) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index bce3405d0..976953b31 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -91,21 +91,21 @@ def generate(wwan): wwan['device'] = find_device_file(wwan['device']) # Create PPP configuration files - render(config_wwan, 'wwan/peer.tmpl', wwan, trim_blocks=True) + render(config_wwan, 'wwan/peer.tmpl', wwan) # Create PPP chat script - render(config_wwan_chat, 'wwan/chat.tmpl', wwan, trim_blocks=True) + render(config_wwan_chat, 'wwan/chat.tmpl', wwan) # generated script file must be executable # Create script for ip-pre-up.d render(script_wwan_pre_up, 'wwan/ip-pre-up.script.tmpl', - wwan, trim_blocks=True, permission=0o755) + wwan, permission=0o755) # Create script for ip-up.d render(script_wwan_ip_up, 'wwan/ip-up.script.tmpl', - wwan, trim_blocks=True, permission=0o755) + wwan, permission=0o755) # Create script for ip-down.d render(script_wwan_ip_down, 'wwan/ip-down.script.tmpl', - wwan, trim_blocks=True, permission=0o755) + wwan, permission=0o755) return None diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py index 11a5b7aaa..a65e8b567 100755 --- a/src/conf_mode/ipsec-settings.py +++ b/src/conf_mode/ipsec-settings.py @@ -170,12 +170,12 @@ def verify(data): raise ConfigError("L2TP VPN configuration error: \"vpn ipsec ipsec-interfaces\" must be specified.") def generate(data): - render(charon_conf_file, 'ipsec/charon.tmpl', data, trim_blocks=True) + render(charon_conf_file, 'ipsec/charon.tmpl', data) if data["ipsec_l2tp"]: remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_secrets_file) # old_umask = os.umask(0o077) - # render(ipsec_secrets_file, 'ipsec/ipsec.secrets.tmpl', data, trim_blocks=True) + # render(ipsec_secrets_file, 'ipsec/ipsec.secrets.tmpl', data) # os.umask(old_umask) ## Use this method while IPSec CLI handler won't be overwritten to python write_ipsec_secrets(data) @@ -186,12 +186,12 @@ def generate(data): if not os.path.exists(ipsec_ra_conn_dir): os.makedirs(ipsec_ra_conn_dir) - render(ipsec_ra_conn_file, 'ipsec/remote-access.tmpl', data, trim_blocks=True) + render(ipsec_ra_conn_file, 'ipsec/remote-access.tmpl', data) os.umask(old_umask) remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_file) # old_umask = os.umask(0o077) - # render(ipsec_conf_file, 'ipsec/ipsec.conf.tmpl', data, trim_blocks=True) + # render(ipsec_conf_file, 'ipsec/ipsec.conf.tmpl', data) # os.umask(old_umask) ## Use this method while IPSec CLI handler won't be overwritten to python write_ipsec_conf(data) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index b66cd370a..f5c023b81 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -18,18 +18,19 @@ import jmespath import json import os -from copy import deepcopy from distutils.version import LooseVersion from platform import release as kernel_version 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 call from vyos.util import cmd from vyos.util import check_kmod +from vyos.util import dict_search from vyos.validate import is_addr_assigned +from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -40,17 +41,6 @@ if LooseVersion(kernel_version()) > LooseVersion('5.1'): else: k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] -default_config_data = { - 'deleted': False, - 'destination': [], - 'helper_functions': None, - 'pre_ct_helper': '', - 'pre_ct_conntrack': '', - 'out_ct_helper': '', - 'out_ct_conntrack': '', - 'source': [] -} - iptables_nat_config = '/tmp/vyos-nat-rules.nft' def get_handler(json, chain, target): @@ -66,114 +56,43 @@ def get_handler(json, chain, target): return None -def verify_rule(rule, err_msg): +def verify_rule(config, err_msg): """ Common verify steps used for both source and destination NAT """ - if rule['translation_port'] or rule['dest_port'] or rule['source_port']: - if rule['protocol'] not in ['tcp', 'udp', 'tcp_udp']: - proto = rule['protocol'] - raise ConfigError(f'{err_msg} ports can only be specified when protocol is "tcp", "udp" or "tcp_udp" (currently "{proto}")') - if '/' in rule['translation_address']: + if (dict_search('translation.port', config) != None or + dict_search('destination.port', config) != None or + dict_search('source.port', config)): + + if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']: + raise ConfigError(f'{err_msg}\n' \ + 'ports can only be specified when protocol is '\ + 'either tcp, udp or tcp_udp!') + + if '/' in (dict_search('translation.address', config) or []): raise ConfigError(f'{err_msg}\n' \ 'Cannot use ports with an IPv4net type translation address as it\n' \ 'statically maps a whole network of addresses onto another\n' \ 'network of addresses') - -def parse_configuration(conf, source_dest): - """ Common wrapper to read in both NAT source and destination CLI """ - ruleset = [] - base_level = ['nat', source_dest] - conf.set_level(base_level) - for number in conf.list_nodes(['rule']): - rule = { - 'description': '', - 'dest_address': '', - 'dest_port': '', - 'disabled': False, - 'exclude': False, - 'interface_in': '', - 'interface_out': '', - 'log': False, - 'protocol': 'all', - 'number': number, - 'source_address': '', - 'source_prefix': '', - 'source_port': '', - 'translation_address': '', - 'translation_prefix': '', - 'translation_port': '' - } - conf.set_level(base_level + ['rule', number]) - - if conf.exists(['description']): - rule['description'] = conf.return_value(['description']) - - if conf.exists(['destination', 'address']): - tmp = conf.return_value(['destination', 'address']) - if tmp.startswith('!'): - tmp = tmp.replace('!', '!=') - rule['dest_address'] = tmp - - if conf.exists(['destination', 'port']): - tmp = conf.return_value(['destination', 'port']) - if tmp.startswith('!'): - tmp = tmp.replace('!', '!=') - rule['dest_port'] = tmp - - if conf.exists(['disable']): - rule['disabled'] = True - - if conf.exists(['exclude']): - rule['exclude'] = True - - if conf.exists(['inbound-interface']): - rule['interface_in'] = conf.return_value(['inbound-interface']) - - if conf.exists(['outbound-interface']): - rule['interface_out'] = conf.return_value(['outbound-interface']) - - if conf.exists(['log']): - rule['log'] = True - - if conf.exists(['protocol']): - rule['protocol'] = conf.return_value(['protocol']) - - if conf.exists(['source', 'address']): - tmp = conf.return_value(['source', 'address']) - if tmp.startswith('!'): - tmp = tmp.replace('!', '!=') - rule['source_address'] = tmp - - if conf.exists(['source', 'prefix']): - rule['source_prefix'] = conf.return_value(['source', 'prefix']) - - if conf.exists(['source', 'port']): - tmp = conf.return_value(['source', 'port']) - if tmp.startswith('!'): - tmp = tmp.replace('!', '!=') - rule['source_port'] = tmp - - if conf.exists(['translation', 'address']): - rule['translation_address'] = conf.return_value(['translation', 'address']) - - if conf.exists(['translation', 'prefix']): - rule['translation_prefix'] = conf.return_value(['translation', 'prefix']) - - if conf.exists(['translation', 'port']): - rule['translation_port'] = conf.return_value(['translation', 'port']) - - ruleset.append(rule) - - return ruleset - def get_config(config=None): - nat = deepcopy(default_config_data) if config: conf = config else: conf = Config() + base = ['nat'] + 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']) + 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 raw') nftable_json = json.loads(tmp) @@ -182,7 +101,7 @@ def get_config(config=None): 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(['nat']): + if not conf.exists(base): nat['helper_functions'] = 'remove' # Retrieve current table handler positions @@ -190,9 +109,7 @@ def get_config(config=None): nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK') nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_HELPER') nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK') - - nat['deleted'] = True - + nat['deleted'] = '' return nat # check if NAT connection tracking helpers need to be set up - this has to @@ -206,19 +123,10 @@ def get_config(config=None): nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_IGNORE') nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_OUTPUT_HOOK') - # set config level for parsing in NAT configuration - conf.set_level(['nat']) - - # use a common wrapper function to read in the source / destination - # tree from the config - thus we do not need to replicate almost the - # same code :-) - for tgt in ['source', 'destination', 'nptv6']: - nat[tgt] = parse_configuration(conf, tgt) - return nat def verify(nat): - if nat['deleted']: + if not nat or 'deleted' in nat: # no need to verify the CLI as NAT is going to be deactivated return None @@ -226,49 +134,55 @@ def verify(nat): if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']): raise Exception('could not determine nftable ruleset handlers') - for rule in nat['source']: - interface = rule['interface_out'] - err_msg = f'Source NAT configuration error in rule "{rule["number"]}":' - - if interface and interface not in 'any' and interface not in interfaces(): - print(f'Warning: rule "{rule["number"]}" interface "{interface}" does not exist on this system') + if dict_search('source.rule', nat): + for rule, config in dict_search('source.rule', nat).items(): + err_msg = f'Source NAT 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 'any' and config['outbound_interface'] not in interfaces(): + print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') - if not rule['interface_out']: - raise ConfigError(f'{err_msg} outbound-interface not specified') - if rule['translation_address']: - addr = rule['translation_address'] - if addr != 'masquerade': - for ip in addr.split('-'): - if not is_addr_assigned(ip): - print(f'Warning: IP address {ip} does not exist on the system!') + addr = dict_search('translation.address', config) + if addr != None: + if addr != 'masquerade': + for ip in addr.split('-'): + if not is_addr_assigned(ip): + print(f'WARNING: IP address {ip} does not exist on the system!') + elif 'exclude' not in config: + raise ConfigError(f'{err_msg}\n' \ + 'translation address not specified') - elif not rule['exclude']: - raise ConfigError(f'{err_msg} translation address not specified') + # common rule verification + verify_rule(config, err_msg) - # common rule verification - verify_rule(rule, err_msg) - for rule in nat['destination']: - interface = rule['interface_in'] - err_msg = f'Destination NAT configuration error in rule "{rule["number"]}":' + if dict_search('destination.rule', nat): + for rule, config in dict_search('destination.rule', nat).items(): + err_msg = f'Destination NAT configuration error in rule {rule}:' - if interface and interface not in 'any' and interface not in interfaces(): - print(f'Warning: rule "{rule["number"]}" interface "{interface}" does not exist on this system') + 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') - if not rule['interface_in']: - raise ConfigError(f'{err_msg} inbound-interface not specified') - if not rule['translation_address'] and not rule['exclude']: - raise ConfigError(f'{err_msg} translation address not specified') + if dict_search('translation.address', config) == None and 'exclude' not in config: + raise ConfigError(f'{err_msg}\n' \ + 'translation address not specified') - # common rule verification - verify_rule(rule, err_msg) + # common rule verification + verify_rule(config, err_msg) return None def generate(nat): - render(iptables_nat_config, 'firewall/nftables-nat.tmpl', nat, trim_blocks=True, permission=0o755) + render(iptables_nat_config, 'firewall/nftables-nat.tmpl', nat, + permission=0o755) return None def apply(nat): diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index d6453ec83..b102b3e9e 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -53,8 +53,8 @@ def generate(ntp): if not ntp: return None - render(config_file, 'ntp/ntp.conf.tmpl', ntp, trim_blocks=True) - render(systemd_override, 'ntp/override.conf.tmpl', ntp, trim_blocks=True) + render(config_file, 'ntp/ntp.conf.tmpl', ntp) + render(systemd_override, 'ntp/override.conf.tmpl', ntp) return None diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py new file mode 100755 index 000000000..c4024dce4 --- /dev/null +++ b/src/conf_mode/policy-local-route.py @@ -0,0 +1,110 @@ +#!/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 + +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import node_changed +from vyos.configdict import leaf_node_changed +from vyos.template import render +from vyos.util import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +def get_config(config=None): + + if config: + conf = config + else: + conf = Config() + base = ['policy', 'local-route'] + pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # delete policy local-route + dict = {} + tmp = node_changed(conf, ['policy', 'local-route', 'rule']) + if tmp: + for rule in (tmp or []): + src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) + if src: + dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) + pbr.update(dict) + + # delete policy local-route rule x source x.x.x.x + if 'rule' in pbr: + for rule in pbr['rule']: + src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) + if src: + dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) + pbr.update(dict) + + return pbr + +def verify(pbr): + # bail out early - looks like removal from running config + if not pbr: + return None + + if 'rule' in pbr: + for rule in pbr['rule']: + if 'source' not in pbr['rule'][rule]: + raise ConfigError('Source address required!') + else: + if 'set' not in pbr['rule'][rule] or 'table' not in pbr['rule'][rule]['set']: + raise ConfigError('Table set is required!') + + return None + +def generate(pbr): + if not pbr: + return None + + return None + +def apply(pbr): + if not pbr: + return None + + # Delete old rule if needed + if 'rule_remove' in pbr: + for rule in pbr['rule_remove']: + for src in pbr['rule_remove'][rule]['source']: + call(f'ip rule del prio {rule} from {src}') + + # Generate new config + if 'rule' in pbr: + for rule in pbr['rule']: + table = pbr['rule'][rule]['set']['table'] + if pbr['rule'][rule]['source']: + for src in pbr['rule'][rule]['source']: + call(f'ip rule add prio {rule} from {src} lookup {table}') + + 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/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 654874232..642738b09 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -14,16 +14,16 @@ # 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.util import call +from vyos.util import dict_search from vyos.template import render from vyos.template import render_to_string +from vyos import ConfigError from vyos import frr -from vyos import ConfigError, airbag +from vyos import airbag airbag.enable() config_file = r'/tmp/bgp.frr' @@ -31,8 +31,10 @@ config_file = r'/tmp/bgp.frr' def get_config(): conf = Config() base = ['protocols', 'nbgp'] - bgp = conf.get_config_dict(base, key_mangling=('-', '_')) + bgp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # XXX: any reason we can not move this into the FRR template? + # we shall not call vtysh directly, especially not in get_config() if not conf.exists(base): bgp = {} call('vtysh -c \"conf t\" -c \"no ip protocol bgp\" ') @@ -40,9 +42,6 @@ def get_config(): if not conf.exists(base + ['route-map']): call('vtysh -c \"conf t\" -c \"no ip protocol bgp\" ') - from pprint import pprint - pprint(bgp) - return bgp def verify(bgp): @@ -50,9 +49,23 @@ def verify(bgp): return None # Check if declared more than one ASN - for asn in bgp['nbgp'].items(): - if len(bgp['nbgp']) > 1: - raise ConfigError('Only one bgp ASN process can be definded') + if len(bgp) > 1: + raise ConfigError('Only one BGP AS can be defined!') + + for asn, asn_config in bgp.items(): + # Common verification for both peer-group and neighbor statements + for neigh in ['neighbor', 'peer_group']: + # bail out early if there is no neighbor or peer-group statement + # this also saves one indention level + if neigh not in asn_config: + continue + + for neighbor, config in asn_config[neigh].items(): + if 'remote_as' not in config and 'peer_group' not in config: + raise ConfigError(f'BGP remote-as must be specified for "{neighbor}"!') + + if 'remote_as' in config and 'peer_group' in config: + raise ConfigError(f'BGP peer-group member "{neighbor}" cannot override remote-as of peer-group!') return None @@ -61,33 +74,40 @@ def generate(bgp): bgp['new_frr_config'] = '' return None - # render(config) not needed, its only for debug - render(config_file, 'frr/bgp.frr.tmpl', bgp) + # only one BGP AS is supported, so we can directly send the first key + # of the config dict + asn = list(bgp.keys())[0] + bgp[asn]['asn'] = asn - bgp['new_frr_config'] = render_to_string('frr/bgp.frr.tmpl', bgp) + # render(config) not needed, its only for debug + render(config_file, 'frr/bgp.frr.tmpl', bgp[asn]) + bgp['new_frr_config'] = render_to_string('frr/bgp.frr.tmpl', bgp[asn]) return None def apply(bgp): - # Save original configration prior to starting any commit actions - bgp['original_config'] = frr.get_configuration(daemon='bgpd') - bgp['modified_config'] = frr.replace_section(bgp['original_config'], bgp['new_frr_config'], from_re='router bgp .*') + # Save original configuration prior to starting any commit actions + frr_cfg = {} + frr_cfg['original_config'] = frr.get_configuration(daemon='bgpd') + frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], bgp['new_frr_config'], from_re='router bgp .*') # Debugging + print('') print('--------- DEBUGGING ----------') - print(f'Existing config:\n{bgp["original_config"]}\n\n') + print(f'Existing config:\n{frr_cfg["original_config"]}\n\n') print(f'Replacement config:\n{bgp["new_frr_config"]}\n\n') - print(f'Modified config:\n{bgp["modified_config"]}\n\n') + print(f'Modified config:\n{frr_cfg["modified_config"]}\n\n') - # Frr Mark configuration will test for syntax errors and exception out if any syntax errors are detected - frr.mark_configuration(bgp['modified_config']) + # FRR mark configuration will test for syntax errors and throws an + # exception if any syntax errors is detected + frr.mark_configuration(frr_cfg['modified_config']) - # Commit the resulting new configuration to frr, this will render an frr.CommitError() Exception on fail - frr.reload_configuration(bgp['modified_config'], daemon='bgpd') + # Commit resulting configuration to FRR, this will throw CommitError + # on failure + frr.reload_configuration(frr_cfg['modified_config'], daemon='bgpd') return None - if __name__ == '__main__': try: c = get_config() diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py index 6f4fc784d..8606e7364 100755 --- a/src/conf_mode/protocols_igmp.py +++ b/src/conf_mode/protocols_igmp.py @@ -21,8 +21,9 @@ from sys import exit from vyos import ConfigError from vyos.config import Config -from vyos.util import call +from vyos.util import call, process_named_running from vyos.template import render +from signal import SIGTERM from vyos import airbag airbag.enable() @@ -36,12 +37,20 @@ def get_config(config=None): conf = Config() igmp_conf = { 'igmp_conf' : False, + 'pim_conf' : False, + 'igmp_proxy_conf' : False, 'old_ifaces' : {}, 'ifaces' : {} } if not (conf.exists('protocols igmp') or conf.exists_effective('protocols igmp')): return None + if conf.exists('protocols igmp-proxy'): + igmp_conf['igmp_proxy_conf'] = True + + if conf.exists('protocols pim'): + igmp_conf['pim_conf'] = True + if conf.exists('protocols igmp'): igmp_conf['igmp_conf'] = True @@ -79,6 +88,10 @@ def verify(igmp): return None if igmp['igmp_conf']: + # Check conflict with IGMP-Proxy + if igmp['igmp_proxy_conf']: + raise ConfigError(f"IGMP proxy and PIM cannot be both configured at the same time") + # Check interfaces if not igmp['ifaces']: raise ConfigError(f"IGMP require defined interfaces!") @@ -99,9 +112,16 @@ def apply(igmp): if igmp is None: return None - if os.path.exists(config_file): - call(f'vtysh -d pimd -f {config_file}') - os.remove(config_file) + pim_pid = process_named_running('pimd') + if igmp['igmp_conf'] or igmp['pim_conf']: + if not pim_pid: + call(f'pimd -d -F traditional --daemon -A 127.0.0.1') + + if os.path.exists(config_file): + call(f'vtysh -d pimd -f {config_file}') + os.remove(config_file) + elif pim_pid: + os.kill(int(pim_pid), SIGTERM) return None diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index b0b8d705b..bd372a7b3 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -107,16 +107,15 @@ def generate(isis): isis[process]['process'] = process # render(config) not needed, its only for debug - render(config_file, 'frr/isis.frr.tmpl', isis[process], trim_blocks=True) + render(config_file, 'frr/isis.frr.tmpl', isis[process]) isis['new_frr_config'] = render_to_string('frr/isis.frr.tmpl', - isis[process], trim_blocks=True) + isis[process]) return None def apply(isis): - - # Save original configration prior to starting any commit actions + # Save original configuration prior to starting any commit actions frr_cfg = {} frr_cfg['original_config'] = frr.get_configuration(daemon='isisd') frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], isis['new_frr_config'], from_re='interface .*') diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 84948baf4..791b18110 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# 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 @@ -16,351 +16,124 @@ import os +from sys import exit + from vyos.config import Config -from vyos import ConfigError +from vyos.configdict import node_changed +from vyos.template import render_to_string from vyos.util import call -from vyos.template import render - +from vyos.util import dict_search +from vyos import ConfigError +from vyos import frr from vyos import airbag airbag.enable() config_file = r'/tmp/ldpd.frr' -def sysctl(name, value): - call('sysctl -wq {}={}'.format(name, value)) - def get_config(config=None): if config: conf = config else: conf = Config() - mpls_conf = { - 'router_id' : None, - 'mpls_ldp' : False, - 'old_parameters' : { - 'no_ttl_propagation' : False, - 'maximum_ttl' : None - }, - 'parameters' : { - 'no_ttl_propagation' : False, - 'maximum_ttl' : None - }, - 'old_ldp' : { - 'interfaces' : [], - 'neighbors' : {}, - 'd_transp_ipv4' : None, - 'd_transp_ipv6' : None, - 'hello_ipv4_holdtime' : None, - 'hello_ipv4_interval' : None, - 'hello_ipv6_holdtime' : None, - 'hello_ipv6_interval' : None, - 'ses_ipv4_hold' : None, - 'ses_ipv6_hold' : None, - 'export_ipv4_exp' : False, - 'export_ipv6_exp' : False, - 'cisco_interop_tlv' : False, - 'transport_prefer_ipv4' : False, - 'target_ipv4_addresses' : [], - 'target_ipv6_addresses' : [], - 'target_ipv4_enable' : False, - 'target_ipv6_enable' : False, - 'target_ipv4_hello_int' : None, - 'target_ipv6_hello_int' : None, - 'target_ipv4_hello_hold' : None, - 'target_ipv6_hello_hold' : None - }, - 'ldp' : { - 'interfaces' : [], - 'neighbors' : {}, - 'd_transp_ipv4' : None, - 'd_transp_ipv6' : None, - 'hello_ipv4_holdtime' : None, - 'hello_ipv4_interval' : None, - 'hello_ipv6_holdtime' : None, - 'hello_ipv6_interval' : None, - 'ses_ipv4_hold' : None, - 'ses_ipv6_hold' : None, - 'export_ipv4_exp' : False, - 'export_ipv6_exp' : False, - 'cisco_interop_tlv' : False, - 'transport_prefer_ipv4' : False, - 'target_ipv4_addresses' : [], - 'target_ipv6_addresses' : [], - 'target_ipv4_enable' : False, - 'target_ipv6_enable' : False, - 'target_ipv4_hello_int' : None, - 'target_ipv6_hello_int' : None, - 'target_ipv4_hello_hold' : None, - 'target_ipv6_hello_hold' : None - } - } - if not (conf.exists('protocols mpls') or conf.exists_effective('protocols mpls')): - return None - - # If LDP is defined then enable LDP portion of code - if conf.exists('protocols mpls ldp'): - mpls_conf['mpls_ldp'] = True - - # Set to MPLS hierarchy configuration level - conf.set_level('protocols mpls') - - # Get no_ttl_propagation - if conf.exists_effective('parameters no-propagate-ttl'): - mpls_conf['old_parameters']['no_ttl_propagation'] = True - - if conf.exists('parameters no-propagate-ttl'): - mpls_conf['parameters']['no_ttl_propagation'] = True - - # Get maximum_ttl - if conf.exists_effective('parameters maximum-ttl'): - mpls_conf['old_parameters']['maximum_ttl'] = conf.return_effective_value('parameters maximum-ttl') - - if conf.exists('parameters maximum-ttl'): - mpls_conf['parameters']['maximum_ttl'] = conf.return_value('parameters maximum-ttl') - - # Set to LDP hierarchy configuration level - conf.set_level('protocols mpls ldp') - - # Get router-id - if conf.exists_effective('router-id'): - mpls_conf['old_router_id'] = conf.return_effective_value('router-id') - - if conf.exists('router-id'): - mpls_conf['router_id'] = conf.return_value('router-id') - - # Get hello-ipv4-holdtime - if conf.exists_effective('discovery hello-ipv4-holdtime'): - mpls_conf['old_ldp']['hello_ipv4_holdtime'] = conf.return_effective_value('discovery hello-ipv4-holdtime') - - if conf.exists('discovery hello-ipv4-holdtime'): - mpls_conf['ldp']['hello_ipv4_holdtime'] = conf.return_value('discovery hello-ipv4-holdtime') - - # Get hello-ipv4-interval - if conf.exists_effective('discovery hello-ipv4-interval'): - mpls_conf['old_ldp']['hello_ipv4_interval'] = conf.return_effective_value('discovery hello-ipv4-interval') - - if conf.exists('discovery hello-ipv4-interval'): - mpls_conf['ldp']['hello_ipv4_interval'] = conf.return_value('discovery hello-ipv4-interval') - - # Get hello-ipv6-holdtime - if conf.exists_effective('discovery hello-ipv6-holdtime'): - mpls_conf['old_ldp']['hello_ipv6_holdtime'] = conf.return_effective_value('discovery hello-ipv6-holdtime') - - if conf.exists('discovery hello-ipv6-holdtime'): - mpls_conf['ldp']['hello_ipv6_holdtime'] = conf.return_value('discovery hello-ipv6-holdtime') - - # Get hello-ipv6-interval - if conf.exists_effective('discovery hello-ipv6-interval'): - mpls_conf['old_ldp']['hello_ipv6_interval'] = conf.return_effective_value('discovery hello-ipv6-interval') - - if conf.exists('discovery hello-ipv6-interval'): - mpls_conf['ldp']['hello_ipv6_interval'] = conf.return_value('discovery hello-ipv6-interval') - - # Get session-ipv4-holdtime - if conf.exists_effective('discovery session-ipv4-holdtime'): - mpls_conf['old_ldp']['ses_ipv4_hold'] = conf.return_effective_value('discovery session-ipv4-holdtime') - - if conf.exists('discovery session-ipv4-holdtime'): - mpls_conf['ldp']['ses_ipv4_hold'] = conf.return_value('discovery session-ipv4-holdtime') - - # Get session-ipv6-holdtime - if conf.exists_effective('discovery session-ipv6-holdtime'): - mpls_conf['old_ldp']['ses_ipv6_hold'] = conf.return_effective_value('discovery session-ipv6-holdtime') - - if conf.exists('discovery session-ipv6-holdtime'): - mpls_conf['ldp']['ses_ipv6_hold'] = conf.return_value('discovery session-ipv6-holdtime') - - # Get discovery transport-ipv4-address - if conf.exists_effective('discovery transport-ipv4-address'): - mpls_conf['old_ldp']['d_transp_ipv4'] = conf.return_effective_value('discovery transport-ipv4-address') - - if conf.exists('discovery transport-ipv4-address'): - mpls_conf['ldp']['d_transp_ipv4'] = conf.return_value('discovery transport-ipv4-address') - - # Get discovery transport-ipv6-address - if conf.exists_effective('discovery transport-ipv6-address'): - mpls_conf['old_ldp']['d_transp_ipv6'] = conf.return_effective_value('discovery transport-ipv6-address') - - if conf.exists('discovery transport-ipv6-address'): - mpls_conf['ldp']['d_transp_ipv6'] = conf.return_value('discovery transport-ipv6-address') - - # Get export ipv4 explicit-null - if conf.exists_effective('export ipv4 explicit-null'): - mpls_conf['old_ldp']['export_ipv4_exp'] = True - - if conf.exists('export ipv4 explicit-null'): - mpls_conf['ldp']['export_ipv4_exp'] = True - - # Get export ipv6 explicit-null - if conf.exists_effective('export ipv6 explicit-null'): - mpls_conf['old_ldp']['export_ipv6_exp'] = True - - if conf.exists('export ipv6 explicit-null'): - mpls_conf['ldp']['export_ipv6_exp'] = True - - # Get target_ipv4_addresses - if conf.exists_effective('targeted-neighbor ipv4 address'): - mpls_conf['old_ldp']['target_ipv4_addresses'] = conf.return_effective_values('targeted-neighbor ipv4 address') + base = ['protocols', 'mpls'] - if conf.exists('targeted-neighbor ipv4 address'): - mpls_conf['ldp']['target_ipv4_addresses'] = conf.return_values('targeted-neighbor ipv4 address') - - # Get target_ipv4_enable - if conf.exists_effective('targeted-neighbor ipv4 enable'): - mpls_conf['old_ldp']['target_ipv4_enable'] = True - - if conf.exists('targeted-neighbor ipv4 enable'): - mpls_conf['ldp']['target_ipv4_enable'] = True - - # Get target_ipv4_hello_int - if conf.exists_effective('targeted-neighbor ipv4 hello-interval'): - mpls_conf['old_ldp']['target_ipv4_hello_int'] = conf.return_effective_value('targeted-neighbor ipv4 hello-interval') - - if conf.exists('targeted-neighbor ipv4 hello-interval'): - mpls_conf['ldp']['target_ipv4_hello_int'] = conf.return_value('targeted-neighbor ipv4 hello-interval') - - # Get target_ipv4_hello_hold - if conf.exists_effective('targeted-neighbor ipv4 hello-holdtime'): - mpls_conf['old_ldp']['target_ipv4_hello_hold'] = conf.return_effective_value('targeted-neighbor ipv4 hello-holdtime') - - if conf.exists('targeted-neighbor ipv4 hello-holdtime'): - mpls_conf['ldp']['target_ipv4_hello_hold'] = conf.return_value('targeted-neighbor ipv4 hello-holdtime') - - # Get target_ipv6_addresses - if conf.exists_effective('targeted-neighbor ipv6 address'): - mpls_conf['old_ldp']['target_ipv6_addresses'] = conf.return_effective_values('targeted-neighbor ipv6 address') - - if conf.exists('targeted-neighbor ipv6 address'): - mpls_conf['ldp']['target_ipv6_addresses'] = conf.return_values('targeted-neighbor ipv6 address') - - # Get target_ipv6_enable - if conf.exists_effective('targeted-neighbor ipv6 enable'): - mpls_conf['old_ldp']['target_ipv6_enable'] = True - - if conf.exists('targeted-neighbor ipv6 enable'): - mpls_conf['ldp']['target_ipv6_enable'] = True - - # Get target_ipv6_hello_int - if conf.exists_effective('targeted-neighbor ipv6 hello-interval'): - mpls_conf['old_ldp']['target_ipv6_hello_int'] = conf.return_effective_value('targeted-neighbor ipv6 hello-interval') - - if conf.exists('targeted-neighbor ipv6 hello-interval'): - mpls_conf['ldp']['target_ipv6_hello_int'] = conf.return_value('targeted-neighbor ipv6 hello-interval') - - # Get target_ipv6_hello_hold - if conf.exists_effective('targeted-neighbor ipv6 hello-holdtime'): - mpls_conf['old_ldp']['target_ipv6_hello_hold'] = conf.return_effective_value('targeted-neighbor ipv6 hello-holdtime') - - if conf.exists('targeted-neighbor ipv6 hello-holdtime'): - mpls_conf['ldp']['target_ipv6_hello_hold'] = conf.return_value('targeted-neighbor ipv6 hello-holdtime') - - # Get parameters cisco-interop-tlv - if conf.exists_effective('parameters cisco-interop-tlv'): - mpls_conf['old_ldp']['cisco_interop_tlv'] = True - - if conf.exists('parameters cisco-interop-tlv'): - mpls_conf['ldp']['cisco_interop_tlv'] = True - - # Get parameters transport-prefer-ipv4 - if conf.exists_effective('parameters transport-prefer-ipv4'): - mpls_conf['old_ldp']['transport_prefer_ipv4'] = True - - if conf.exists('parameters transport-prefer-ipv4'): - mpls_conf['ldp']['transport_prefer_ipv4'] = True - - # Get interfaces - if conf.exists_effective('interface'): - mpls_conf['old_ldp']['interfaces'] = conf.return_effective_values('interface') - - if conf.exists('interface'): - mpls_conf['ldp']['interfaces'] = conf.return_values('interface') - - # Get neighbors - for neighbor in conf.list_effective_nodes('neighbor'): - mpls_conf['old_ldp']['neighbors'].update({ - neighbor : { - 'password' : conf.return_effective_value('neighbor {0} password'.format(neighbor), default=''), - 'ttl_security' : conf.return_effective_value('neighbor {0} ttl-security'.format(neighbor), default=''), - 'session_holdtime' : conf.return_effective_value('neighbor {0} session-holdtime'.format(neighbor), default='') - } - }) - - for neighbor in conf.list_nodes('neighbor'): - mpls_conf['ldp']['neighbors'].update({ - neighbor : { - 'password' : conf.return_value('neighbor {0} password'.format(neighbor), default=''), - 'ttl_security' : conf.return_value('neighbor {0} ttl-security'.format(neighbor), default=''), - 'session_holdtime' : conf.return_value('neighbor {0} session-holdtime'.format(neighbor), default='') - } - }) - - return mpls_conf - -def operate_mpls_on_intfc(interfaces, action): - rp_filter = 0 - if action == 1: - rp_filter = 2 - for iface in interfaces: - sysctl('net.mpls.conf.{0}.input'.format(iface), action) - # Operate rp filter - sysctl('net.ipv4.conf.{0}.rp_filter'.format(iface), rp_filter) + mpls = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + return mpls def verify(mpls): - if mpls is None: + # If no config, then just bail out early. + if not mpls: return None - if mpls['mpls_ldp']: - # Require router-id - if not mpls['router_id']: - raise ConfigError(f"MPLS ldp router-id is mandatory!") + # Checks to see if LDP is properly configured + if 'ldp' in mpls: + # If router ID not defined + if 'router_id' not in mpls['ldp']: + raise ConfigError('Router ID missing. An LDP router id is mandatory!') - # Require discovery transport-address - if not mpls['ldp']['d_transp_ipv4'] and not mpls['ldp']['d_transp_ipv6']: - raise ConfigError(f"MPLS ldp discovery transport address is mandatory!") + # If interface not set + if 'interface' not in mpls['ldp']: + raise ConfigError('LDP interfaces are missing. An LDP interface is mandatory!') - # Require interface - if not mpls['ldp']['interfaces']: - raise ConfigError(f"MPLS ldp interface is mandatory!") + # If transport addresses are not set + if not dict_search('ldp.discovery.transport_ipv4_address', mpls) and \ + not dict_search('ldp.discovery.transport_ipv6_address', mpls): + raise ConfigError('LDP transport address missing!') + + return None def generate(mpls): - if mpls is None: + # If there's no MPLS config generated, create dictionary key with no value. + if not mpls: + mpls['new_frr_config'] = '' return None - render(config_file, 'frr/ldpd.frr.tmpl', mpls) + mpls['new_frr_config'] = render_to_string('frr/ldpd.frr.tmpl', mpls) return None def apply(mpls): - if mpls is None: - return None - - # Set number of entries in the platform label table - if mpls['mpls_ldp']: - sysctl('net.mpls.platform_labels', '1048575') + # Define dictionary that will load FRR config + frr_cfg = {} + # Save original configuration prior to starting any commit actions + frr_cfg['original_config'] = frr.get_configuration(daemon='ldpd') + frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], mpls['new_frr_config'], from_re='mpls.*') + + # If FRR config is blank, rerun the blank commit three times due to frr-reload + # behavior/bug not properly clearing out on one commit. + if mpls['new_frr_config'] == '': + for x in range(3): + frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') + elif not 'ldp' in mpls: + for x in range(3): + frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') else: - sysctl('net.mpls.platform_labels', '0') - - # Choose whether to copy IP TTL to MPLS header TTL - if mpls['parameters']['no_ttl_propagation']: - sysctl('net.mpls.ip_ttl_propagate', '0') + # FRR mark configuration will test for syntax errors and throws an + # exception if any syntax errors is detected + frr.mark_configuration(frr_cfg['modified_config']) + + # Commit resulting configuration to FRR, this will throw CommitError + # on failure + frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') + + # Set number of entries in the platform label tables + labels = '0' + if 'interface' in mpls: + labels = '1048575' + call(f'sysctl -wq net.mpls.platform_labels={labels}') + + # Check for changes in global MPLS options + if 'parameters' in mpls: + # Choose whether to copy IP TTL to MPLS header TTL + if 'no_propagate_ttl' in mpls['parameters']: + call('sysctl -wq net.mpls.ip_ttl_propagate=0') + # Choose whether to limit maximum MPLS header TTL + if 'maximum_ttl' in mpls['parameters']: + ttl = mpls['parameters']['maximum_ttl'] + call(f'sysctl -wq net.mpls.default_ttl={ttl}') else: - sysctl('net.mpls.ip_ttl_propagate', '1') - - # Choose whether to limit maximum MPLS header TTL - if mpls['parameters']['maximum_ttl']: - sysctl('net.mpls.default_ttl', '%s' %(mpls['parameters']['maximum_ttl'])) + # Set default global MPLS options if not defined. + call('sysctl -wq net.mpls.ip_ttl_propagate=1') + call('sysctl -wq net.mpls.default_ttl=255') + + # Enable and disable MPLS processing on interfaces per configuration + if 'interface' in mpls: + system_interfaces = [] + system_interfaces.append(((os.popen('sysctl net.mpls.conf').read()).split('\n'))) + del system_interfaces[0][-1] + for configured_interface in mpls['interface']: + for system_interface in system_interfaces[0]: + if configured_interface in system_interface: + call(f'sysctl -wq net.mpls.conf.{configured_interface}.input=1') + elif system_interface.endswith(' = 1'): + system_interface = system_interface.replace(' = 1', '=0') + call(f'sysctl -wq {system_interface}') else: - sysctl('net.mpls.default_ttl', '255') - - # Allow mpls on interfaces - operate_mpls_on_intfc(mpls['ldp']['interfaces'], 1) - - # Disable mpls on deleted interfaces - diactive_ifaces = set(mpls['old_ldp']['interfaces']).difference(mpls['ldp']['interfaces']) - operate_mpls_on_intfc(diactive_ifaces, 0) - - if os.path.exists(config_file): - call(f'vtysh -d ldpd -f {config_file}') - os.remove(config_file) + # If MPLS interfaces are not configured, set MPLS processing disabled + system_interfaces = [] + system_interfaces.append(((os.popen('sysctl net.mpls.conf').read()).replace(" = 1", "=0")).split('\n')) + del system_interfaces[0][-1] + for interface in (system_interfaces[0]): + call(f'sysctl -wq {interface}') return None diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 6d333e19a..8a9f034d5 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -21,8 +21,9 @@ from sys import exit from vyos.config import Config from vyos import ConfigError -from vyos.util import call +from vyos.util import call, process_named_running from vyos.template import render +from signal import SIGTERM from vyos import airbag airbag.enable() @@ -36,6 +37,8 @@ def get_config(config=None): conf = Config() pim_conf = { 'pim_conf' : False, + 'igmp_conf' : False, + 'igmp_proxy_conf' : False, 'old_pim' : { 'ifaces' : {}, 'rp' : {} @@ -48,6 +51,12 @@ def get_config(config=None): if not (conf.exists('protocols pim') or conf.exists_effective('protocols pim')): return None + if conf.exists('protocols igmp-proxy'): + pim_conf['igmp_proxy_conf'] = True + + if conf.exists('protocols igmp'): + pim_conf['igmp_conf'] = True + if conf.exists('protocols pim'): pim_conf['pim_conf'] = True @@ -92,6 +101,10 @@ def verify(pim): return None if pim['pim_conf']: + # Check conflict with IGMP-Proxy + if pim['igmp_proxy_conf']: + raise ConfigError(f"IGMP proxy and PIM cannot be both configured at the same time") + # Check interfaces if not pim['pim']['ifaces']: raise ConfigError(f"PIM require defined interfaces!") @@ -126,9 +139,16 @@ def apply(pim): if pim is None: return None - if os.path.exists(config_file): - call("vtysh -d pimd -f " + config_file) - os.remove(config_file) + pim_pid = process_named_running('pimd') + if pim['igmp_conf'] or pim['pim_conf']: + if not pim_pid: + call(f'pimd -d -F traditional --daemon -A 127.0.0.1') + + if os.path.exists(config_file): + call("vtysh -d pimd -f " + config_file) + os.remove(config_file) + elif pim_pid: + os.kill(int(pim_pid), SIGTERM) return None diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py index 27d0ee60c..67edeb630 100755 --- a/src/conf_mode/service_ids_fastnetmon.py +++ b/src/conf_mode/service_ids_fastnetmon.py @@ -56,7 +56,7 @@ def verify(fastnetmon): if not os.access(fastnetmon["alert_script"], os.X_OK): raise ConfigError('Script {0} does not have permissions for execution'.format(fastnetmon["alert_script"])) else: - raise ConfigError('File {0} does not exists!'.format(fastnetmon["alert_script"])) + raise ConfigError('File {0} does not exists!'.format(fastnetmon["alert_script"])) def generate(fastnetmon): if not fastnetmon: @@ -67,8 +67,8 @@ def generate(fastnetmon): return - render(config_file, 'ids/fastnetmon.tmpl', fastnetmon, trim_blocks=True) - render(networks_list, 'ids/fastnetmon_networks_list.tmpl', fastnetmon, trim_blocks=True) + render(config_file, 'ids/fastnetmon.tmpl', fastnetmon) + render(networks_list, 'ids/fastnetmon_networks_list.tmpl', fastnetmon) return None diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index 68c554360..f676fdbbe 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -283,7 +283,7 @@ def generate(ipoe): if not ipoe: return None - render(ipoe_conf, 'accel-ppp/ipoe.config.tmpl', ipoe, trim_blocks=True) + render(ipoe_conf, 'accel-ppp/ipoe.config.tmpl', ipoe) if ipoe['auth_mode'] == 'local': render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.tmpl', ipoe) diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 2260b3fe1..9fbd531da 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -73,11 +73,11 @@ def generate(pppoe): if not pppoe: return None - render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe, trim_blocks=True) + render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe) if dict_search('authentication.mode', pppoe) == 'local': render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl', - pppoe, trim_blocks=True, permission=0o640) + pppoe, permission=0o640) else: if os.path.exists(pppoe_chap_secrets): os.unlink(pppoe_chap_secrets) diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index 687d7068f..65eb11ce3 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -93,7 +93,7 @@ def generate(rtradv): if not rtradv: return None - render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, trim_blocks=True, permission=0o644) + render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, permission=0o644) return None def apply(rtradv): diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index e07745963..8f99053d2 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -66,8 +66,8 @@ def generate(ssh): return None - render(config_file, 'ssh/sshd_config.tmpl', ssh, trim_blocks=True) - render(systemd_override, 'ssh/override.conf.tmpl', ssh, trim_blocks=True) + render(config_file, 'ssh/sshd_config.tmpl', ssh) + render(systemd_override, 'ssh/override.conf.tmpl', ssh) return None diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 2c0bbd4f7..39bad717d 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -256,7 +256,7 @@ def generate(login): if len(login['radius_server']) > 0: render(radius_config_file, 'system-login/pam_radius_auth.conf.tmpl', - login, trim_blocks=True) + login) uid = getpwnam('root').pw_uid gid = getpwnam('root').pw_gid diff --git a/src/conf_mode/system-options.py b/src/conf_mode/system-option.py index 1061b90ac..910c14474 100755 --- a/src/conf_mode/system-options.py +++ b/src/conf_mode/system-option.py @@ -39,7 +39,7 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['system', 'options'] + base = ['system', 'option'] options = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) # We have gathered the dict representation of the CLI, but there are default @@ -73,13 +73,13 @@ def verify(options): return None def generate(options): - render(curlrc_config, 'system/curlrc.tmpl', options, trim_blocks=True) - render(ssh_config, 'system/ssh_config.tmpl', options, trim_blocks=True) + render(curlrc_config, 'system/curlrc.tmpl', options) + render(ssh_config, 'system/ssh_config.tmpl', options) return None def apply(options): # System bootup beep - if 'beep_if_fully_booted' in options: + if 'startup_beep' in options: cmd('systemctl enable vyos-beep.service') else: cmd('systemctl disable vyos-beep.service') @@ -87,10 +87,10 @@ def apply(options): # Ctrl-Alt-Delete action if os.path.exists(systemd_action_file): os.unlink(systemd_action_file) - if 'ctrl_alt_del_action' in options: - if options['ctrl_alt_del_action'] == 'reboot': + if 'ctrl_alt_del' in options: + if options['ctrl_alt_del'] == 'reboot': os.symlink('/lib/systemd/system/reboot.target', systemd_action_file) - elif options['ctrl_alt_del_action'] == 'poweroff': + elif options['ctrl_alt_del'] == 'poweroff': os.symlink('/lib/systemd/system/poweroff.target', systemd_action_file) # Configure HTTP client @@ -123,7 +123,7 @@ def apply(options): # Keyboard layout - there will be always the default key inside the dict # but we check for key existence anyway if 'keyboard_layout' in options: - cmd('loadkeys -C /dev/console {keyboard_layout}'.format(**options)) + cmd('loadkeys {keyboard_layout}'.format(**options)) if __name__ == '__main__': try: @@ -134,4 +134,3 @@ if __name__ == '__main__': except ConfigError as e: print(e) exit(1) - diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index b1daf7a82..3d8a51cd8 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -203,12 +203,12 @@ def generate(c): return None conf = '/etc/rsyslog.d/vyos-rsyslog.conf' - render(conf, 'syslog/rsyslog.conf.tmpl', c, trim_blocks=True) + render(conf, 'syslog/rsyslog.conf.tmpl', c) # eventually write for each file its own logrotate file, since size is # defined it shouldn't matter conf = '/etc/logrotate.d/vyos-rsyslog' - render(conf, 'syslog/logrotate.tmpl', c, trim_blocks=True) + render(conf, 'syslog/logrotate.tmpl', c) def verify(c): diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py index a540d1b9e..b5ce32beb 100755 --- a/src/conf_mode/system_lcd.py +++ b/src/conf_mode/system_lcd.py @@ -61,9 +61,9 @@ def generate(lcd): lcd['device'] = find_device_file(lcd['device']) # Render config file for daemon LCDd - render(lcdd_conf, 'lcd/LCDd.conf.tmpl', lcd, trim_blocks=True) + render(lcdd_conf, 'lcd/LCDd.conf.tmpl', lcd) # Render config file for client lcdproc - render(lcdproc_conf, 'lcd/lcdproc.conf.tmpl', lcd, trim_blocks=True) + render(lcdproc_conf, 'lcd/lcdproc.conf.tmpl', lcd) return None diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index 56e195b6a..2409eec1f 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -92,7 +92,7 @@ def generate(tftpd): config['listen_address'] = f'[{address}]:{port} -6' file = config_file + str(idx) - render(file, 'tftp-server/default.tmpl', config, trim_blocks=True) + render(file, 'tftp-server/default.tmpl', config) idx = idx + 1 return None diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 80eb8daf2..e970d2ef5 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -357,7 +357,7 @@ def generate(l2tp): if not l2tp: return None - render(l2tp_conf, 'accel-ppp/l2tp.config.tmpl', l2tp, trim_blocks=True) + render(l2tp_conf, 'accel-ppp/l2tp.config.tmpl', l2tp) if l2tp['auth_mode'] == 'local': render(l2tp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', l2tp) diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py index af8604972..b2aa13c0d 100755 --- a/src/conf_mode/vpn_openconnect.py +++ b/src/conf_mode/vpn_openconnect.py @@ -34,12 +34,10 @@ ocserv_passwd = cfg_dir + '/ocpasswd' radius_cfg = cfg_dir + '/radiusclient.conf' radius_servers = cfg_dir + '/radius_servers' - # Generate hash from user cleartext password def get_hash(password): return crypt(password, mksalt(METHOD_SHA512)) - def get_config(): conf = Config() base = ['vpn', 'openconnect'] @@ -47,10 +45,12 @@ def get_config(): return None ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # 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) ocserv = dict_merge(default_values, ocserv) - return ocserv + return ocserv def verify(ocserv): if ocserv is None: @@ -88,7 +88,7 @@ def verify(ocserv): ocserv["network_settings"]["push_route"].remove("0.0.0.0/0") ocserv["network_settings"]["push_route"].append("default") else: - ocserv["network_settings"]["push_route"] = "default" + ocserv["network_settings"]["push_route"] = "default" else: raise ConfigError('openconnect network settings required') @@ -99,19 +99,18 @@ def generate(ocserv): if "radius" in ocserv["authentication"]["mode"]: # Render radius client configuration - render(radius_cfg, 'ocserv/radius_conf.tmpl', ocserv["authentication"]["radius"], trim_blocks=True) + render(radius_cfg, 'ocserv/radius_conf.tmpl', ocserv["authentication"]["radius"]) # Render radius servers - render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"], trim_blocks=True) + render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"]) else: if "local_users" in ocserv["authentication"]: for user in ocserv["authentication"]["local_users"]["username"]: ocserv["authentication"]["local_users"]["username"][user]["hash"] = get_hash(ocserv["authentication"]["local_users"]["username"][user]["password"]) # Render local users - render(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"], trim_blocks=True) + render(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"]) # Render config - render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv, trim_blocks=True) - + render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv) def apply(ocserv): diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index 3125ee9d0..30abe4782 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -264,10 +264,10 @@ def generate(pptp): if not pptp: return None - render(pptp_conf, 'accel-ppp/pptp.config.tmpl', pptp, trim_blocks=True) + render(pptp_conf, 'accel-ppp/pptp.config.tmpl', pptp) if pptp['local_users']: - render(pptp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', pptp, trim_blocks=True) + render(pptp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', pptp) os.chmod(pptp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP) else: if os.path.exists(pptp_chap_secrets): diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index 1b2b80ce5..47367f125 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -82,11 +82,11 @@ def generate(sstp): return None # accel-cmd reload doesn't work so any change results in a restart of the daemon - render(sstp_conf, 'accel-ppp/sstp.config.tmpl', sstp, trim_blocks=True) + render(sstp_conf, 'accel-ppp/sstp.config.tmpl', sstp) if dict_search('authentication.mode', sstp) == 'local': render(sstp_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl', - sstp, trim_blocks=True, permission=0o640) + sstp, permission=0o640) else: if os.path.exists(sstp_chap_secrets): os.unlink(sstp_chap_secrets) diff --git a/src/migration-scripts/interfaces/14-to-15 b/src/migration-scripts/interfaces/14-to-15 new file mode 100755 index 000000000..5c25f8628 --- /dev/null +++ b/src/migration-scripts/interfaces/14-to-15 @@ -0,0 +1,58 @@ +#!/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/>. + +# T3048: remove smp-affinity node from ethernet and use tuned instead + +import os + +from sys import exit, argv +from vyos.configtree import ConfigTree + +if __name__ == '__main__': + 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) + base = ['interfaces', 'ethernet'] + + if not config.exists(base): + # Nothing to do + exit(0) + + migrate = False + for interface in config.list_nodes(base): + smp_base = base + [interface, 'smp-affinity'] + # if any one interface had smp-affinity configured manually, we will + # configure "system option performance" + if config.exists(smp_base): + if config.return_value(smp_base) != 'auto': + migrate = True + config.delete(smp_base) + + if migrate: + config.set(['system', 'options', 'performance'], value='throughput') + + 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/migration-scripts/system/19-to-20 b/src/migration-scripts/system/19-to-20 new file mode 100755 index 000000000..eb20fd8db --- /dev/null +++ b/src/migration-scripts/system/19-to-20 @@ -0,0 +1,64 @@ +#!/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/>. + +# T3048: remove smp-affinity node from ethernet and use tuned instead + +import os + +from sys import exit, argv +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() + +base = ['system', 'options'] +base_new = ['system', 'option'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +if config.exists(base_new): + for node in config.list_nodes(base): + config.copy(base + [node], base_new + [node]) +else: + config.copy(base, base_new) + +config.delete(base) + +# Rename "system option beep-if-fully-booted" -> "system option startup-beep" +base_beep = base_new + ['beep-if-fully-booted'] +if config.exists(base_beep): + config.rename(base_beep, 'startup-beep') + +# Rename "system option ctrl-alt-del-action" -> "system option ctrl-alt-delete" +base_ctrl_alt_del = base_new + ['ctrl-alt-del-action'] +if config.exists(base_ctrl_alt_del): + config.rename(base_ctrl_alt_del, 'ctrl-alt-delete') + + +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/op_mode/dynamic_dns.py b/src/op_mode/dynamic_dns.py index 021acfd73..962943896 100755 --- a/src/op_mode/dynamic_dns.py +++ b/src/op_mode/dynamic_dns.py @@ -26,13 +26,13 @@ from vyos.util import call cache_file = r'/run/ddclient/ddclient.cache' OUT_TMPL_SRC = """ -{%- for entry in hosts -%} +{% for entry in hosts %} ip address : {{ entry.ip }} host-name : {{ entry.host }} last update : {{ entry.time }} update-status: {{ entry.status }} -{% endfor -%} +{% endfor %} """ def show_status(): diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py index 172ce71b7..fa19e7d45 100755 --- a/src/op_mode/lldp_op.py +++ b/src/op_mode/lldp_op.py @@ -117,7 +117,7 @@ if __name__ == '__main__': parser.print_help() exit(1) - tmpl = jinja2.Template(lldp_out, trim_blocks=True) + tmpl = jinja2.Template(lldp_out) config_text = tmpl.render(parse_data(neighbors)) print(config_text) diff --git a/src/op_mode/show_cpu.py b/src/op_mode/show_cpu.py index 0a540da1d..0040e950d 100755 --- a/src/op_mode/show_cpu.py +++ b/src/op_mode/show_cpu.py @@ -21,16 +21,16 @@ from sys import exit from vyos.util import popen, DEVNULL OUT_TMPL_SRC = """ -{%- if cpu -%} -{% if 'vendor' in cpu %}CPU Vendor: {{cpu.vendor}}{%- endif %} -{% if 'model' in cpu %}Model: {{cpu.model}}{%- endif %} -{% if 'cpus' in cpu %}Total CPUs: {{cpu.cpus}}{%- endif %} -{% if 'sockets' in cpu %}Sockets: {{cpu.sockets}}{%- endif %} -{% if 'cores' in cpu %}Cores: {{cpu.cores}}{%- endif %} -{% if 'threads' in cpu %}Threads: {{cpu.threads}}{%- endif %} -{% if 'mhz' in cpu %}Current MHz: {{cpu.mhz}}{%- endif %} -{% if 'mhz_min' in cpu %}Minimum MHz: {{cpu.mhz_min}}{%- endif %} -{% if 'mhz_max' in cpu %}Maximum MHz: {{cpu.mhz_max}}{%- endif %} +{% if cpu %} +{% if 'vendor' in cpu %}CPU Vendor: {{cpu.vendor}}{% endif %} +{% if 'model' in cpu %}Model: {{cpu.model}}{% endif %} +{% if 'cpus' in cpu %}Total CPUs: {{cpu.cpus}}{% endif %} +{% if 'sockets' in cpu %}Sockets: {{cpu.sockets}}{% endif %} +{% if 'cores' in cpu %}Cores: {{cpu.cores}}{% endif %} +{% if 'threads' in cpu %}Threads: {{cpu.threads}}{% endif %} +{% if 'mhz' in cpu %}Current MHz: {{cpu.mhz}}{% endif %} +{% if 'mhz_min' in cpu %}Minimum MHz: {{cpu.mhz_min}}{% endif %} +{% if 'mhz_max' in cpu %}Maximum MHz: {{cpu.mhz_max}}{% endif %} {% endif %} """ diff --git a/src/op_mode/show_igmpproxy.py b/src/op_mode/show_igmpproxy.py index 5ccc16287..4714e494b 100755 --- a/src/op_mode/show_igmpproxy.py +++ b/src/op_mode/show_igmpproxy.py @@ -36,9 +36,9 @@ import vyos.config # eth0.3 0.0b 0 0.0b 0 xxx.xxx.x.7 # tun1 0.0b 0 0.0b 0 xxx.xxx.xxx.2 vif_out_tmpl = """ -{%- for r in data %} +{% for r in data %} {{ "%-10s"|format(r.interface) }} {{ "%-12s"|format(r.bytes_in) }} {{ "%-12s"|format(r.pkts_in) }} {{ "%-12s"|format(r.bytes_out) }} {{ "%-12s"|format(r.pkts_out) }} {{ "%-15s"|format(r.loc) }} -{%- endfor %} +{% endfor %} """ # Output Template for "show ip multicast mfc" command @@ -48,9 +48,9 @@ vif_out_tmpl = """ # xxx.xxx.xxx.250 xxx.xx.xxx.75 -- # xxx.xxx.xx.124 xx.xxx.xxx.26 -- mfc_out_tmpl = """ -{%- for r in data %} +{% for r in data %} {{ "%-15s"|format(r.group) }} {{ "%-15s"|format(r.origin) }} {{ "%-12s"|format(r.pkts) }} {{ "%-12s"|format(r.bytes) }} {{ "%-12s"|format(r.wrong) }} {{ "%-10s"|format(r.iif) }} {{ "%-20s"|format(r.oifs|join(', ')) }} -{%- endfor %} +{% endfor %} """ parser = argparse.ArgumentParser() diff --git a/src/op_mode/show_nat_statistics.py b/src/op_mode/show_nat_statistics.py index 0b53112f2..482993d06 100755 --- a/src/op_mode/show_nat_statistics.py +++ b/src/op_mode/show_nat_statistics.py @@ -26,14 +26,14 @@ OUT_TMPL_SRC=""" rule pkts bytes interface ---- ---- ----- --------- {% for r in output %} -{%- if r.comment -%} -{%- set packets = r.counter.packets -%} -{%- set bytes = r.counter.bytes -%} -{%- set interface = r.interface -%} +{% if r.comment %} +{% set packets = r.counter.packets %} +{% set bytes = r.counter.bytes %} +{% set interface = r.interface %} {# remove rule comment prefix #} -{%- set comment = r.comment | replace('SRC-NAT-', '') | replace('DST-NAT-', '') | replace(' tcp_udp', '') -%} +{% set comment = r.comment | replace('SRC-NAT-', '') | replace('DST-NAT-', '') | replace(' tcp_udp', '') %} {{ "%-4s" | format(comment) }} {{ "%9s" | format(packets) }} {{ "%12s" | format(bytes) }} {{ interface }} -{%- endif %} +{% endif %} {% endfor %} """ diff --git a/src/op_mode/show_openvpn.py b/src/op_mode/show_openvpn.py index 1da4c7ecb..f7b99cc0d 100755 --- a/src/op_mode/show_openvpn.py +++ b/src/op_mode/show_openvpn.py @@ -28,9 +28,9 @@ OpenVPN status on {{ intf }} Client CN Remote Host Local Host TX bytes RX bytes Connected Since --------- ----------- ---------- -------- -------- --------------- -{%- for c in clients %} +{% for c in clients %} {{ "%-15s"|format(c.name) }} {{ "%-21s"|format(c.remote) }} {{ "%-21s"|format(local) }} {{ "%-9s"|format(c.tx_bytes) }} {{ "%-9s"|format(c.rx_bytes) }} {{ c.online_since }} -{%- endfor %} +{% endfor %} {% endif %} """ diff --git a/src/op_mode/show_usb_serial.py b/src/op_mode/show_usb_serial.py index 776898c25..973bf19c8 100755 --- a/src/op_mode/show_usb_serial.py +++ b/src/op_mode/show_usb_serial.py @@ -22,9 +22,9 @@ from sys import exit OUT_TMPL_SRC = """Device Model Vendor ------ ------ ------ -{%- for d in devices %} +{% for d in devices %} {{ "%-16s" | format(d.device) }} {{ "%-19s" | format(d.model)}} {{d.vendor}} -{%- endfor %} +{% endfor %} """ diff --git a/src/op_mode/show_vrf.py b/src/op_mode/show_vrf.py index b6bb73d01..94358c6e4 100755 --- a/src/op_mode/show_vrf.py +++ b/src/op_mode/show_vrf.py @@ -23,9 +23,9 @@ from vyos.util import cmd vrf_out_tmpl = """ VRF name state mac address flags interfaces -------- ----- ----------- ----- ---------- -{%- for v in vrf %} +{% for v in vrf %} {{"%-16s"|format(v.ifname)}} {{ "%-8s"|format(v.operstate | lower())}} {{"%-17s"|format(v.address | lower())}} {{ v.flags|join(',')|lower()}} {{v.members|join(',')|lower()}} -{%- endfor %} +{% endfor %} """ diff --git a/src/op_mode/show_wireless.py b/src/op_mode/show_wireless.py index b5ee3aee1..19ab6771c 100755 --- a/src/op_mode/show_wireless.py +++ b/src/op_mode/show_wireless.py @@ -28,7 +28,6 @@ parser.add_argument("-s", "--scan", help="Scan for Wireless APs on given interfa parser.add_argument("-b", "--brief", action="store_true", help="Show wireless configuration") parser.add_argument("-c", "--stations", help="Show wireless clients connected on interface, e.g. 'wlan0'") - def show_brief(): config = Config() if len(config.list_effective_nodes('interfaces wireless')) == 0: @@ -37,17 +36,11 @@ def show_brief(): interfaces = [] for intf in config.list_effective_nodes('interfaces wireless'): - config.set_level('interfaces wireless {}'.format(intf)) - data = { - 'name': intf, - 'type': '', - 'ssid': '', - 'channel': '' - } - data['type'] = config.return_effective_value('type') - data['ssid'] = config.return_effective_value('ssid') - data['channel'] = config.return_effective_value('channel') - + config.set_level(f'interfaces wireless {intf}') + data = { 'name': intf } + data['type'] = config.return_effective_value('type') or '-' + data['ssid'] = config.return_effective_value('ssid') or '-' + data['channel'] = config.return_effective_value('channel') or '-' interfaces.append(data) return interfaces diff --git a/src/op_mode/snmp_v3.py b/src/op_mode/snmp_v3.py index 92601f15e..a1f76f0bc 100755 --- a/src/op_mode/snmp_v3.py +++ b/src/op_mode/snmp_v3.py @@ -37,7 +37,7 @@ SNMPv3 Groups: Group View ----- ---- - {% if group -%}{% for g in group -%} + {% if group %}{% for g in group %} {{ "%-20s" | format(g.name) }}{{ g.view }}({{ g.mode }}) {% endfor %}{% endif %} """ @@ -47,7 +47,7 @@ SNMPv3 Trap-targets: Tpap-target Port Protocol Auth Priv Type EngineID User ----------- ---- -------- ---- ---- ---- -------- ---- - {% if trap -%}{% for t in trap -%} + {% if trap %}{% for t in trap %} {{ "%-20s" | format(t.name) }} {{ t.port }} {{ t.proto }} {{ t.auth }} {{ t.priv }} {{ t.type }} {{ "%-32s" | format(t.engID) }} {{ t.user }} {% endfor %}{% endif %} """ @@ -57,14 +57,14 @@ SNMPv3 Users: User Auth Priv Mode Group ---- ---- ---- ---- ----- - {% if user -%}{% for u in user -%} + {% if user %}{% for u in user %} {{ "%-20s" | format(u.name) }}{{ u.auth }} {{ u.priv }} {{ u.mode }} {{ u.group }} {% endfor %}{% endif %} """ VIEW_OUTP_TMPL_SRC = """ SNMPv3 Views: - {% if view -%}{% for v in view %} + {% if view %}{% for v in view %} View : {{ v.name }} OIDs : .{{ v.oids | join("\n .")}} {% endfor %}{% endif %} diff --git a/src/systemd/isc-dhcp-relay.service b/src/systemd/isc-dhcp-relay.service index 56bcec840..de2e51a87 100644 --- a/src/systemd/isc-dhcp-relay.service +++ b/src/systemd/isc-dhcp-relay.service @@ -3,7 +3,7 @@ Description=ISC DHCP IPv4 relay Documentation=man:dhcrelay(8) Wants=network-online.target RequiresMountsFor=/run -ConditionPathExists=/run/dhcp-relay/dhcp.conf +ConditionPathExists=/run/dhcp-relay/dhcrelay.conf After=vyos-router.service [Service] @@ -11,7 +11,7 @@ Type=forking WorkingDirectory=/run/dhcp-relay RuntimeDirectory=dhcp-relay RuntimeDirectoryPreserve=yes -EnvironmentFile=/run/dhcp-relay/dhcp.conf +EnvironmentFile=/run/dhcp-relay/dhcrelay.conf PIDFile=/run/dhcp-relay/dhcrelay.pid ExecStart=/usr/sbin/dhcrelay -4 -pf /run/dhcp-relay/dhcrelay.pid $OPTIONS Restart=always diff --git a/src/systemd/isc-dhcp-relay6.service b/src/systemd/isc-dhcp-relay6.service index 85ff16e41..30037e013 100644 --- a/src/systemd/isc-dhcp-relay6.service +++ b/src/systemd/isc-dhcp-relay6.service @@ -3,7 +3,7 @@ Description=ISC DHCP IPv6 relay Documentation=man:dhcrelay(8) Wants=network-online.target RequiresMountsFor=/run -ConditionPathExists=/run/dhcp-relay/dhcpv6.conf +ConditionPathExists=/run/dhcp-relay/dhcrelay6.conf After=vyos-router.service [Service] @@ -11,9 +11,9 @@ Type=forking WorkingDirectory=/run/dhcp-relay RuntimeDirectory=dhcp-relay RuntimeDirectoryPreserve=yes -EnvironmentFile=/run/dhcp-relay/dhcpv6.conf -PIDFile=/run/dhcp-relay/dhcrelayv6.pid -ExecStart=/usr/sbin/dhcrelay -6 -pf /run/dhcp-relay/dhcrelayv6.pid $OPTIONS +EnvironmentFile=/run/dhcp-relay/dhcrelay6.conf +PIDFile=/run/dhcp-relay/dhcrelay6.pid +ExecStart=/usr/sbin/dhcrelay -6 -pf /run/dhcp-relay/dhcrelay6.pid $OPTIONS Restart=always [Install] |