diff options
76 files changed, 1347 insertions, 1045 deletions
diff --git a/data/templates/accel-ppp/chap-secrets.pppoe.tmpl b/data/templates/accel-ppp/chap-secrets.config_dict.tmpl index da64b64d5..da64b64d5 100644 --- a/data/templates/accel-ppp/chap-secrets.pppoe.tmpl +++ b/data/templates/accel-ppp/chap-secrets.config_dict.tmpl diff --git a/data/templates/accel-ppp/config_chap_secrets_radius.j2 b/data/templates/accel-ppp/config_chap_secrets_radius.j2 new file mode 100644 index 000000000..c94e75a23 --- /dev/null +++ b/data/templates/accel-ppp/config_chap_secrets_radius.j2 @@ -0,0 +1,36 @@ +{% if authentication.mode is defined and authentication.mode == 'local' %}
+[chap-secrets]
+chap-secrets={{ chap_secrets_file }}
+{% elif authentication.mode is defined and authentication.mode == 'radius' %}
+[radius]
+verbose=1
+{% for server, options in authentication.radius.server.items() if not options.disable is defined %}
+server={{ server }},{{ options.key }},auth-port={{ options.port }},acct-port={{ options.acct_port }},req-limit=0,fail-time={{ options.fail_time }}
+{% endfor %}
+{% if authentication.radius.acct_interim_jitter is defined and authentication.radius.acct_interim_jitter is not none %}
+acct-interim-jitter={{ authentication.radius.acct_interim_jitter }}
+{% endif %}
+acct-timeout={{ authentication.radius.acct_timeout }}
+timeout={{ authentication.radius.timeout }}
+max-try={{ authentication.radius.max_try }}
+{% if authentication.radius.nas_identifier is defined and authentication.radius.nas_identifier is not none %}
+nas-identifier={{ authentication.radius.nas_identifier }}
+{% endif %}
+{% if authentication.radius.nas_ip_address is defined and authentication.radius.nas_ip_address is not none %}
+nas-ip-address={{ authentication.radius.nas_ip_address }}
+{% endif %}
+{% if authentication.radius.source_address is defined and authentication.radius.source_address is not none %}
+bind={{ authentication.radius.source_address }}
+{% endif %}
+{% if authentication.radius.called_sid_format is defined and authentication.radius.called_sid_format is not none %}
+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 %}
+{# Both chap-secrets and radius block required the gw-ip-address #}
+{% if gateway_address is defined and gateway_address is not none %}
+gw-ip-address={{ gateway_address }}
+{% endif %}
+
diff --git a/data/templates/accel-ppp/config_ipv6_pool.j2 b/data/templates/accel-ppp/config_ipv6_pool.j2 index b764fc6f0..f45bf9442 100644 --- a/data/templates/accel-ppp/config_ipv6_pool.j2 +++ b/data/templates/accel-ppp/config_ipv6_pool.j2 @@ -13,4 +13,8 @@ delegate={{ prefix }},{{ options.delegation_prefix }} {% endfor %} {% endif %} {% endif %} +{% if client_ipv6_pool.delegate is defined and client_ipv6_pool.delegate is not none %} +[ipv6-dhcp] +verbose=1 +{% endif %} {% endif %} diff --git a/data/templates/accel-ppp/config_modules_auth_mode.j2 b/data/templates/accel-ppp/config_modules_auth_mode.j2 new file mode 100644 index 000000000..5eca76f91 --- /dev/null +++ b/data/templates/accel-ppp/config_modules_auth_mode.j2 @@ -0,0 +1,5 @@ +{% if authentication is defined and authentication.mode is defined and authentication.mode == 'local' %}
+chap-secrets
+{% elif authentication is defined and authentication.mode is defined and authentication.mode == 'radius' %}
+radius
+{% endif %}
diff --git a/data/templates/accel-ppp/config_modules_auth_protocols.j2 b/data/templates/accel-ppp/config_modules_auth_protocols.j2 new file mode 100644 index 000000000..e122d6c48 --- /dev/null +++ b/data/templates/accel-ppp/config_modules_auth_protocols.j2 @@ -0,0 +1,10 @@ +{% for protocol in authentication.protocols %}
+{# this should be fixed in the CLI by a migrator #}
+{% if protocol == 'chap' %}
+auth_chap_md5
+{% elif protocol == 'mschap' %}
+auth_mschap_v1
+{% else %}
+auth_{{ protocol.replace('-', '_') }}
+{% endif %}
+{% endfor %}
diff --git a/data/templates/accel-ppp/config_modules_ipv6.j2 b/data/templates/accel-ppp/config_modules_ipv6.j2 new file mode 100644 index 000000000..e9ea4924b --- /dev/null +++ b/data/templates/accel-ppp/config_modules_ipv6.j2 @@ -0,0 +1,5 @@ +{% if ppp_options.ipv6 is defined and ppp_options.ipv6 != 'deny' %}
+ipv6pool
+ipv6_nd
+ipv6_dhcp
+{% endif %}
diff --git a/data/templates/accel-ppp/config_shaper_radius.j2 b/data/templates/accel-ppp/config_shaper_radius.j2 new file mode 100644 index 000000000..2a6641245 --- /dev/null +++ b/data/templates/accel-ppp/config_shaper_radius.j2 @@ -0,0 +1,10 @@ +{% if authentication is defined and authentication.mode is defined and authentication.mode == 'radius' %}
+{% if authentication is defined and authentication.radius is defined and authentication.radius.rate_limit is defined and authentication.radius.rate_limit.enable is defined %}
+[shaper]
+verbose=1
+attr={{ authentication.radius.rate_limit.attribute }}
+{% if authentication.radius.rate_limit.vendor is defined and authentication.radius.rate_limit.vendor is not none %}
+vendor={{ authentication.radius.rate_limit.vendor }}
+{% endif %}
+{% endif %}
+{% endif %}
diff --git a/data/templates/accel-ppp/ipoe.config.tmpl b/data/templates/accel-ppp/ipoe.config.tmpl index 5086c386e..ab61f7f5a 100644 --- a/data/templates/accel-ppp/ipoe.config.tmpl +++ b/data/templates/accel-ppp/ipoe.config.tmpl @@ -33,6 +33,7 @@ noauth=1 username=ifname password=csid {% endif %} +proxy-arp=1 {%- for interface in interfaces %} {% if (interface.shared == '0') and (interface.vlan_mon) %} diff --git a/data/templates/accel-ppp/pppoe.config.tmpl b/data/templates/accel-ppp/pppoe.config.tmpl index 8f1b9e7c5..19adbc890 100644 --- a/data/templates/accel-ppp/pppoe.config.tmpl +++ b/data/templates/accel-ppp/pppoe.config.tmpl @@ -2,25 +2,15 @@ [modules] log_syslog pppoe -{{ "radius" if authentication.mode is defined and authentication.mode == 'radius' }} -chap-secrets -ippool -{% if ppp_options.ipv6 is defined and ppp_options.ipv6 != 'deny' %} -ipv6pool -ipv6_nd -ipv6_dhcp -{% endif %} -{% for protocol in authentication.protocols %} -{# this should be fixed in the CLI by a migrator #} -{% if protocol == 'chap' %} -auth_chap_md5 -{% elif protocol == 'mschap' %} -auth_mschap_v1 -{% else %} -auth_{{ protocol.replace('-', '_') }} -{% endif %} -{% endfor %} shaper +{# Common authentication backend definitions #} +{% include 'accel-ppp/config_modules_auth_mode.j2' %} +ippool +{# Common IPv6 definitions #} +{% include 'accel-ppp/config_modules_ipv6.j2' %} +{# Common authentication protocols (pap, chap ...) #} +{% include 'accel-ppp/config_modules_auth_protocols.j2' %} + {% if snmp is defined %} net-snmp {% endif %} @@ -60,41 +50,8 @@ wins{{ loop.index }}={{ server }} {% endfor %} {% endif %} -{% if authentication.mode is defined and authentication.mode == 'local' %} -[chap-secrets] -chap-secrets={{ chap_secrets_file }} -{% elif authentication.mode is defined and authentication.mode == 'radius' %} -[radius] -verbose=1 -{% for server, options in authentication.radius.server.items() if not options.disable is defined %} -server={{ server }},{{ options.key }},auth-port={{ options.port }},acct-port={{ options.acct_port }},req-limit=0,fail-time={{ options.fail_time }} -{% endfor %} -{% if authentication.radius.acct_interim_jitter is defined and authentication.radius.acct_interim_jitter is not none %} -acct-interim-jitter={{ authentication.radius.acct_interim_jitter }} -{% endif %} -acct-timeout={{ authentication.radius.acct_timeout }} -timeout={{ authentication.radius.timeout }} -max-try={{ authentication.radius.max_try }} -{% if authentication.radius.nas_identifier is defined and authentication.radius.nas_identifier is not none %} -nas-identifier={{ authentication.radius.nas_identifier }} -{% endif %} -{% if authentication.radius.nas_ip_address is defined and authentication.radius.nas_ip_address is not none %} -nas-ip-address={{ authentication.radius.nas_ip_address }} -{% endif %} -{% if authentication.radius.source_address is defined and authentication.radius.source_address is not none %} -bind={{ authentication.radius.source_address }} -{% endif %} -{% if authentication.radius.called_sid_format is defined and authentication.radius.called_sid_format is not none %} -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 %} - -{% if gateway_address is defined and gateway_address is not none %} -gw-ip-address={{ gateway_address }} -{% endif %} +{# Common chap-secrets and RADIUS server/option definitions #} +{% include 'accel-ppp/config_chap_secrets_radius.j2' %} {% if session_control is defined and session_control != 'disable' %} [common] @@ -170,5 +127,8 @@ timeout={{ limits.timeout }} {% endif %} {% endif %} +{# Common RADIUS shaper configuration #} +{% include 'accel-ppp/config_shaper_radius.j2' %} + [cli] tcp=127.0.0.1:2001 diff --git a/data/templates/accel-ppp/sstp.config.tmpl b/data/templates/accel-ppp/sstp.config.tmpl index c9e4a1d7d..7ca7b1c1e 100644 --- a/data/templates/accel-ppp/sstp.config.tmpl +++ b/data/templates/accel-ppp/sstp.config.tmpl @@ -3,22 +3,16 @@ log_syslog sstp shaper -{% if auth_mode == 'local' %} -chap-secrets -{% elif auth_mode == 'radius' %} -radius -{% endif -%} +{# Common authentication backend definitions #} +{% include 'accel-ppp/config_modules_auth_mode.j2' %} ippool -ipv6pool -ipv6_nd -ipv6_dhcp - -{% for proto in auth_proto %} -{{proto}} -{% endfor %} +{# Common IPv6 definitions #} +{% include 'accel-ppp/config_modules_ipv6.j2' %} +{# Common authentication protocols (pap, chap ...) #} +{% include 'accel-ppp/config_modules_auth_protocols.j2' %} [core] -thread-count={{thread_cnt}} +thread-count={{ thread_count }} [common] single-session=replace @@ -35,112 +29,37 @@ disable verbose=1 ifname=sstp%d accept=ssl -ssl-ca-file={{ ssl_ca }} -ssl-pemfile={{ ssl_cert }} -ssl-keyfile={{ ssl_key }} - -{% if client_ip_pool %} -[ip-pool] -gw-ip-address={{ client_gateway }} -{% for subnet in client_ip_pool %} -{{ subnet }} -{% endfor %} -{% endif %} +ssl-ca-file={{ ssl.ca_cert_file }} +ssl-pemfile={{ ssl.cert_file }} +ssl-keyfile={{ ssl.key_file }} -{% if dnsv4 %} -[dns] -{% for dns in dnsv4 -%} -dns{{ loop.index }}={{ dns }} -{% endfor -%} -{% endif %} +{# Common IP pool definitions #} +{% include 'accel-ppp/config_ip_pool.j2' %} -{% if dnsv6 %} -[ipv6-dns] -{% for dns in dnsv6 -%} -{{ dns }} -{% endfor -%} -{% endif %} +{# Common IPv6 pool definitions #} +{% include 'accel-ppp/config_ipv6_pool.j2' %} +{# Common DNS name-server definition #} +{% include 'accel-ppp/config_name_server.j2' %} -{% if auth_mode == 'local' %} -[chap-secrets] -chap-secrets={{ chap_secrets_file }} -{% elif auth_mode == 'radius' %} -[radius] -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 -%} - -acct-timeout={{ radius_acct_tmo }} -timeout={{ radius_timeout }} -max-try={{ radius_max_try }} - -{% if radius_nas_id %} -nas-identifier={{ radius_nas_id }} -{% endif -%} -{% if radius_nas_ip %} -nas-ip-address={{ radius_nas_ip }} -{% endif -%} -{% if radius_source_address %} -bind={{ radius_source_address }} -{% endif -%} - - -{% if radius_dynamic_author %} -dae-server={{ radius_dynamic_author.server }}:{{ radius_dynamic_author.port }},{{ radius_dynamic_author.key }} -{% endif -%} -{% endif %} -{% if client_gateway %} -gw-ip-address={{ client_gateway }} -{% endif %} +{# Common chap-secrets and RADIUS server/option definitions #} +{% include 'accel-ppp/config_chap_secrets_radius.j2' %} [ppp] verbose=1 check-ip=1 -{% if mtu %} +{# MTU #} mtu={{ mtu }} -{% endif -%} -{% if client_ipv6_pool %} +{% if client_ipv6_pool is defined %} ipv6=allow {% endif %} +mppe={{ ppp_options.mppe }} +lcp-echo-interval={{ ppp_options.lcp_echo_interval }} +lcp-echo-timeout={{ ppp_options.lcp_echo_timeout }} +lcp-echo-failure={{ ppp_options.lcp_echo_failure }} -{% if ppp_mppe %} -mppe={{ ppp_mppe }} -{% endif -%} -{% if ppp_echo_interval %} -lcp-echo-interval={{ ppp_echo_interval }} -{% endif -%} -{% if ppp_echo_failure %} -lcp-echo-failure={{ ppp_echo_failure }} -{% endif -%} -{% if ppp_echo_timeout %} -lcp-echo-timeout={{ ppp_echo_timeout }} -{% endif %} - -{% if client_ipv6_pool %} -[ipv6-pool] -{% for p in client_ipv6_pool %} -{{ p.prefix }},{{ p.mask }} -{% endfor %} -{% for p in client_ipv6_delegate_prefix %} -delegate={{ p.prefix }},{{ p.mask }} -{% endfor %} -{% endif %} - -{% if client_ipv6_delegate_prefix %} -[ipv6-dhcp] -verbose=1 -{% endif %} - -{% if radius_shaper_attr %} -[shaper] -verbose=1 -attr={{ radius_shaper_attr }} -{% if radius_shaper_vendor %} -vendor={{ radius_shaper_vendor }} -{% endif -%} -{% endif %} +{# Common RADIUS shaper configuration #} +{% include 'accel-ppp/config_shaper_radius.j2' %} [cli] tcp=127.0.0.1:2005 diff --git a/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl b/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl index ff7822b0d..bdeea71da 100644 --- a/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl +++ b/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl @@ -12,6 +12,15 @@ option dhcp6.preference {{ preference }}; {% for network in shared_network %} {%- if not network.disabled -%} shared-network {{ network.name }} { + {%- if network.common.info_refresh_time %} + option dhcp6.info-refresh-time {{ network.common.info_refresh_time }}; + {%- endif %} + {%- if network.common.domain_search %} + option dhcp6.domain-search "{{ network.common.domain_search | join('", "') }}"; + {%- endif %} + {%- if network.common.dns_server %} + option dhcp6.name-servers {{ network.common.dns_server | join(', ') }}; + {%- endif %} {%- for subnet in network.subnet %} subnet6 {{ subnet.network }} { {%- for range in subnet.range6_prefix %} diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl index b0ae3cc61..8799718b0 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.tmpl @@ -10,8 +10,8 @@ threads=1 allow-from={{ allow_from | join(',') }} log-common-errors=yes non-local-bind=yes -query-local-address=0.0.0.0 -query-local-address6=:: +query-local-address={{ source_address_v4 | join(',') }} +query-local-address6={{ source_address_v6 | join(',') }} lua-config-file=recursor.conf.lua # cache-size diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 0c29f536b..286c21859 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -28,6 +28,9 @@ add rule ip raw NAT_CONNTRACK counter accept {% endif %} {% macro nat_rule(rule, 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 %} @@ -45,13 +48,15 @@ add rule ip raw NAT_CONNTRACK counter accept {% set dst_port = "dport { " + rule.dest_port +" }" if rule.dest_port %} {% endif %} -{% set comment = "DST-NAT-" + rule.number %} - {% 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 %} {% 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 %} @@ -72,7 +77,6 @@ add rule ip raw NAT_CONNTRACK counter accept {% endif %} {% if rule.log %} -{% set base_log = "[NAT-DST-" + rule.number %} {% if rule.exclude %} {% set log = base_log + "-EXCL]" %} {% elif rule.translation_address == 'masquerade' %} diff --git a/data/templates/frr/ldpd.frr.tmpl b/data/templates/frr/ldpd.frr.tmpl index dbaa917e8..5f080d75f 100644 --- a/data/templates/frr/ldpd.frr.tmpl +++ b/data/templates/frr/ldpd.frr.tmpl @@ -15,6 +15,12 @@ neighbor {{neighbor_id}} password {{ldp.neighbors[neighbor_id].password}} {% endfor -%} 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 -%} +label local advertise explicit-null +{% endif -%} {% if old_ldp.d_transp_ipv4 -%} no discovery transport-address {{ old_ldp.d_transp_ipv4 }} {% endif -%} @@ -33,6 +39,12 @@ no discovery hello interval {{ old_ldp.hello_interval }} {% if ldp.hello_interval -%} discovery hello interval {{ ldp.hello_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 -%} {% for interface in old_ldp.interfaces -%} no interface {{interface}} {% endfor -%} @@ -46,6 +58,18 @@ exit-address-family {% if ldp.d_transp_ipv6 -%} 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 -%} +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 -%} diff --git a/data/templates/syslog/rsyslog.conf.tmpl b/data/templates/syslog/rsyslog.conf.tmpl index bc3f7667b..a610d132f 100644 --- a/data/templates/syslog/rsyslog.conf.tmpl +++ b/data/templates/syslog/rsyslog.conf.tmpl @@ -22,19 +22,23 @@ $outchannel {{file}},{{files[file]['log-file']}},{{files[file]['max-size']}},{{f {% if hosts %} ## remote logging {% for host in hosts %} -{% if hosts[host]['proto'] == 'tcp' %} -{% if hosts[host]['port'] %} +{% if hosts[host]['proto'] == 'tcp' %} +{% if hosts[host]['port'] %} +{% if hosts[host]['oct_count'] %} +{{hosts[host]['selectors']}} @@(o){{host}}:{{hosts[host]['port']}};RSYSLOG_SyslogProtocol23Format +{% else %} {{hosts[host]['selectors']}} @@{{host}}:{{hosts[host]['port']}} -{% else %} +{% endif %} +{% else %} {{hosts[host]['selectors']}} @@{{host}} -{% endif %} -{% else %} -{% if hosts[host]['port'] %} +{% endif %} +{% else %} +{% if hosts[host]['port'] %} {{hosts[host]['selectors']}} @{{host}}:{{hosts[host]['port']}} -{% else %} +{% else %} {{hosts[host]['selectors']}} @{{host}} -{% endif %} -{% endif %} +{% endif %} +{% endif %} {% endfor %} {% endif %} {% if user %} diff --git a/data/templates/tftp-server/default.tmpl b/data/templates/tftp-server/default.tmpl index 18fee35d1..6b2d6a903 100644 --- a/data/templates/tftp-server/default.tmpl +++ b/data/templates/tftp-server/default.tmpl @@ -1,2 +1,2 @@ ### Autogenerated by tftp_server.py ### -DAEMON_ARGS="--listen --user tftp --address {% for a in listen-%}{{ a }}{% endfor %}{% if allow_upload %} --create --umask 000{% endif %} --secure {{ directory }}" +DAEMON_ARGS="--listen --user tftp --address {{ listen_address }} {{ "--create --umask 000" if allow_upload is defined }} --secure {{ directory }}" diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in index e8bdff3df..ca8abc036 100644 --- a/interface-definitions/dhcp-server.xml.in +++ b/interface-definitions/dhcp-server.xml.in @@ -140,15 +140,14 @@ </leafNode> <leafNode name="domain-name"> <properties> - <help>Client domain name</help> - </properties> - </leafNode> - <leafNode name="domain-search"> - <properties> - <help>Client domain search</help> - <multi/> + <help>Client Domain Name</help> + <constraint> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid domain name (RFC 1123 section 2).\nMay only contain letters, numbers and .-_</constraintErrorMessage> </properties> </leafNode> + #include <include/dhcp-server-domain-search.xml.i> <leafNode name="exclude"> <properties> <help>IP address to exclude from DHCP lease range</help> diff --git a/interface-definitions/dhcpv6-server.xml.in b/interface-definitions/dhcpv6-server.xml.in index 4073b46b2..0b4bcf9ea 100644 --- a/interface-definitions/dhcpv6-server.xml.in +++ b/interface-definitions/dhcpv6-server.xml.in @@ -43,6 +43,39 @@ <valueless/> </properties> </leafNode> + <node name="common-options"> + <properties> + <help>Common options to distribute to all clients, including stateless clients</help> + </properties> + <children> + <leafNode name="info-refresh-time"> + <properties> + <help>Time (in seconds) that stateless clients should wait between refreshing the information they were given</help> + <valueHelp> + <format>1-4294967295</format> + <description>DHCPv6 information refresh time</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4294967295"/> + </constraint> + </properties> + </leafNode> + #include <include/dhcp-server-domain-search.xml.i> + <leafNode name="name-server"> + <properties> + <help>IPv6 address of a Recursive DNS Server</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of DNS name server</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> <tagNode name="subnet"> <properties> <help>IPv6 DHCP subnet for this shared network [REQUIRED]</help> @@ -108,16 +141,7 @@ </tagNode> </children> </node> - <leafNode name="domain-search"> - <properties> - <help>Domain name for client to search</help> - <constraint> - <regex>[-_a-zA-Z0-9.]+</regex> - </constraint> - <constraintErrorMessage>Invalid domain name. May only contain letters, numbers and .-_</constraintErrorMessage> - <multi/> - </properties> - </leafNode> + #include <include/dhcp-server-domain-search.xml.i> <node name="lease-time"> <properties> <help>Parameters relating to the lease time</help> diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index 07e63d54a..ad6eb2463 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -128,24 +128,7 @@ </constraint> </properties> </leafNode> - <leafNode name="listen-address"> - <properties> - <help>Addresses to listen for DNS queries [REQUIRED]</help> - <valueHelp> - <format>ipv4</format> - <description>Domain Name Server (DNS) IPv4 address</description> - </valueHelp> - <valueHelp> - <format>ipv6</format> - <description>Domain Name Server (DNS) IPv6 address</description> - </valueHelp> - <multi/> - <constraint> - <validator name="ipv4-address"/> - <validator name="ipv6-address"/> - </constraint> - </properties> - </leafNode> + #include <include/listen-address.xml.i> <leafNode name="negative-ttl"> <properties> <help>Maximum amount of time negative entries are cached (default: 3600)</help> @@ -177,6 +160,27 @@ </constraint> </properties> </leafNode> + <leafNode name="source-address"> + <properties> + <help>Local addresses from which to send DNS queries. + If unspecified, the querier will use any available address on + the outbound interface.</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address from which to send traffic</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address from which to send traffic</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv6-address"/> + </constraint> + </properties> + <defaultValue>0.0.0.0 ::</defaultValue> + </leafNode> <leafNode name="system"> <properties> <help>Use system name servers</help> diff --git a/interface-definitions/include/accel-auth-local-users.xml.i b/interface-definitions/include/accel-auth-local-users.xml.i index 4dc6c6dff..0d66b8135 100644 --- a/interface-definitions/include/accel-auth-local-users.xml.i +++ b/interface-definitions/include/accel-auth-local-users.xml.i @@ -35,7 +35,7 @@ <properties> <help>Upload bandwidth limit in kbits/sec</help> <constraint> - <validator name="numeric" argument="--range 1-65535"/> + <validator name="numeric" argument="--range 1-10000000"/> </constraint> </properties> </leafNode> @@ -43,7 +43,7 @@ <properties> <help>Download bandwidth limit in kbits/sec</help> <constraint> - <validator name="numeric" argument="--range 1-65535"/> + <validator name="numeric" argument="--range 1-10000000"/> </constraint> </properties> </leafNode> diff --git a/interface-definitions/include/accel-radius-additions-rate-limit.xml.i b/interface-definitions/include/accel-radius-additions-rate-limit.xml.i index deab40e03..23a4a51cf 100644 --- a/interface-definitions/include/accel-radius-additions-rate-limit.xml.i +++ b/interface-definitions/include/accel-radius-additions-rate-limit.xml.i @@ -8,6 +8,7 @@ <properties> <help>Specifies which radius attribute contains rate information. (default is Filter-Id)</help> </properties> + <defaultValue>Filter-Id</defaultValue> </leafNode> <leafNode name="vendor"> <properties> diff --git a/interface-definitions/include/dhcp-server-domain-search.xml.i b/interface-definitions/include/dhcp-server-domain-search.xml.i new file mode 100644 index 000000000..9b3568b72 --- /dev/null +++ b/interface-definitions/include/dhcp-server-domain-search.xml.i @@ -0,0 +1,12 @@ +<!-- included start from dhcp-server-domain-search.xml.i --> +<leafNode name="domain-search"> + <properties> + <help>Client Domain Name search list</help> + <constraint> + <validator name="fqdn"/> + </constraint> + <constraintErrorMessage>Invalid domain name (RFC 1123 section 2).\nMay only contain letters, numbers, period, and underscore.</constraintErrorMessage> + <multi/> + </properties> +</leafNode> +<!-- included end --> diff --git a/interface-definitions/include/interface-disable-forwarding.xml.i b/interface-definitions/include/interface-disable-forwarding.xml.i new file mode 100644 index 000000000..7cbb726ec --- /dev/null +++ b/interface-definitions/include/interface-disable-forwarding.xml.i @@ -0,0 +1,8 @@ +<!-- included start from interface-disable-forwarding.xml.i --> +<leafNode name="disable-forwarding"> + <properties> + <help>Disable IPv4 forwarding on this interface</help> + <valueless/> + </properties> +</leafNode> +<!-- included end --> diff --git a/interface-definitions/include/interface-ipv4.xml.i b/interface-definitions/include/interface-ipv4.xml.i index 551059247..66842ab9b 100644 --- a/interface-definitions/include/interface-ipv4.xml.i +++ b/interface-definitions/include/interface-ipv4.xml.i @@ -5,6 +5,7 @@ </properties> <children> #include <include/interface-disable-arp-filter.xml.i> + #include <include/interface-disable-forwarding.xml.i> #include <include/interface-enable-arp-accept.xml.i> #include <include/interface-enable-arp-announce.xml.i> #include <include/interface-enable-arp-ignore.xml.i> diff --git a/interface-definitions/include/listen-address.xml.i b/interface-definitions/include/listen-address.xml.i new file mode 100644 index 000000000..e474344e4 --- /dev/null +++ b/interface-definitions/include/listen-address.xml.i @@ -0,0 +1,20 @@ +<!-- included start from listen-address.xml.i --> +<leafNode name="listen-address"> + <properties> + <help>Local IP addresses for service to listen on</help> + <valueHelp> + <format>ipv4</format> + <description>IP address to listen for incoming connections</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address to listen for incoming connections</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv6-address"/> + </constraint> + </properties> +</leafNode> +<!-- included end --> diff --git a/interface-definitions/include/vif.xml.i b/interface-definitions/include/vif.xml.i index 15c453fcc..a0f7c0bc8 100644 --- a/interface-definitions/include/vif.xml.i +++ b/interface-definitions/include/vif.xml.i @@ -47,6 +47,7 @@ <children> #include <include/interface-arp-cache-timeout.xml.i> #include <include/interface-disable-arp-filter.xml.i> + #include <include/interface-disable-forwarding.xml.i> #include <include/interface-enable-arp-accept.xml.i> #include <include/interface-enable-arp-announce.xml.i> #include <include/interface-enable-arp-ignore.xml.i> diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index b28be387b..4e2c61d07 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -84,6 +84,7 @@ <children> #include <include/interface-arp-cache-timeout.xml.i> #include <include/interface-disable-arp-filter.xml.i> + #include <include/interface-disable-forwarding.xml.i> #include <include/interface-enable-arp-accept.xml.i> #include <include/interface-enable-arp-announce.xml.i> #include <include/interface-enable-arp-ignore.xml.i> diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 92356d696..0a777865b 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -5,7 +5,7 @@ <tagNode name="bridge" owner="${vyos_conf_scripts_dir}/interfaces-bridge.py"> <properties> <help>Bridge Interface</help> - <priority>489</priority> + <priority>310</priority> <constraint> <regex>^br[0-9]+$</regex> </constraint> @@ -85,6 +85,7 @@ <children> #include <include/interface-arp-cache-timeout.xml.i> #include <include/interface-enable-arp-accept.xml.i> + #include <include/interface-disable-forwarding.xml.i> #include <include/interface-enable-arp-announce.xml.i> #include <include/interface-enable-arp-ignore.xml.i> #include <include/interface-disable-arp-filter.xml.i> diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index 0aef0d332..a19a766d3 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -63,6 +63,7 @@ <children> #include <include/interface-arp-cache-timeout.xml.i> #include <include/interface-disable-arp-filter.xml.i> + #include <include/interface-disable-forwarding.xml.i> #include <include/interface-enable-arp-accept.xml.i> #include <include/interface-enable-arp-announce.xml.i> #include <include/interface-enable-arp-ignore.xml.i> diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index 28df42220..320dfd64d 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -28,6 +28,12 @@ #include <include/interface-enable-proxy-arp.xml.i> </children> </node> + <node name="ipv6"> + <children> + #include <include/ipv6-disable-forwarding.xml.i> + #include <include/ipv6-dup-addr-detect-transmits.xml.i> + </children> + </node> #include <include/interface-mac.xml.i> #include <include/interface-mtu-1450-9000.xml.i> <leafNode name="remote"> diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index 4382db598..3fceb70b6 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -27,6 +27,7 @@ <children> #include <include/interface-arp-cache-timeout.xml.i> #include <include/interface-disable-arp-filter.xml.i> + #include <include/interface-disable-forwarding.xml.i> #include <include/interface-enable-arp-accept.xml.i> #include <include/interface-enable-arp-announce.xml.i> #include <include/interface-enable-arp-ignore.xml.i> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 67001174f..7fdead16a 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -39,6 +39,7 @@ <children> #include <include/interface-arp-cache-timeout.xml.i> #include <include/interface-disable-arp-filter.xml.i> + #include <include/interface-disable-forwarding.xml.i> #include <include/interface-enable-arp-accept.xml.i> #include <include/interface-enable-arp-announce.xml.i> #include <include/interface-enable-arp-ignore.xml.i> diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 8c594e758..423ec7ba2 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -465,6 +465,7 @@ <children> #include <include/interface-arp-cache-timeout.xml.i> #include <include/interface-disable-arp-filter.xml.i> + #include <include/interface-disable-forwarding.xml.i> #include <include/interface-enable-arp-accept.xml.i> #include <include/interface-enable-arp-announce.xml.i> #include <include/interface-enable-arp-ignore.xml.i> @@ -699,6 +700,7 @@ </constraint> <constraintErrorMessage>Unknown WPA mode</constraintErrorMessage> </properties> + <defaultValue>both</defaultValue> </leafNode> <leafNode name="passphrase"> <properties> diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in index 8a14f4d25..00aaddb17 100644 --- a/interface-definitions/nat.xml.in +++ b/interface-definitions/nat.xml.in @@ -42,7 +42,11 @@ <format>ipv4range</format> <description>IPv4 address range to match</description> </valueHelp> - <!-- TODO: add general iptables constraint script --> + <constraint> + <validator name="ipv4-prefix"/> + <validator name="ipv4-address"/> + <validator name="ipv4-range"/> + </constraint> </properties> </leafNode> #include <include/nat-translation-port.xml.i> diff --git a/interface-definitions/ntp.xml.in b/interface-definitions/ntp.xml.in index 485487a42..6070cafe0 100644 --- a/interface-definitions/ntp.xml.in +++ b/interface-definitions/ntp.xml.in @@ -58,24 +58,7 @@ </leafNode> </children> </node> - <leafNode name="listen-address"> - <properties> - <help>Addresses to listen for NTP queries</help> - <valueHelp> - <format>ipv4</format> - <description>Network Time Protocol (NTP) IPv4 address</description> - </valueHelp> - <valueHelp> - <format>ipv6</format> - <description>Network Time Protocol (NTP) IPv6 address</description> - </valueHelp> - <multi/> - <constraint> - <validator name="ipv4-address"/> - <validator name="ipv6-address"/> - </constraint> - </properties> - </leafNode> + #include <include/listen-address.xml.i> #include <include/interface-vrf.xml.i> </children> </node> diff --git a/interface-definitions/protocols-mpls.xml.in b/interface-definitions/protocols-mpls.xml.in index 3e9edbf72..3ea610d8b 100644 --- a/interface-definitions/protocols-mpls.xml.in +++ b/interface-definitions/protocols-mpls.xml.in @@ -78,6 +78,30 @@ </constraint> </properties> </leafNode> + <leafNode name="session-ipv4-holdtime"> + <properties> + <help>Session ipv4 holdtime</help> + <valueHelp> + <format>15-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 15-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="session-ipv6-holdtime"> + <properties> + <help>Session ipv6 holdtime</help> + <valueHelp> + <format>15-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 15-65535"/> + </constraint> + </properties> + </leafNode> <leafNode name="transport-ipv4-address"> <properties> <help>Transport ipv4 address</help> @@ -104,6 +128,39 @@ </leafNode> </children> </node> + <node name="export"> + <properties> + <help>Export parameters</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>IPv4 parameters</help> + </properties> + <children> + <leafNode name="explicit-null"> + <properties> + <help>Explicit-Null Label</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 parameters</help> + </properties> + <children> + <leafNode name="explicit-null"> + <properties> + <help>Explicit-Null Label</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> <leafNode name="interface"> <properties> <help>Listen interface for LDP</help> diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in index d253c2f34..3db740131 100644 --- a/interface-definitions/ssh.xml.in +++ b/interface-definitions/ssh.xml.in @@ -110,24 +110,7 @@ </constraint> </properties> </leafNode> - <leafNode name="listen-address"> - <properties> - <help>Local addresses SSH service should listen on</help> - <valueHelp> - <format>ipv4</format> - <description>IP address to listen for incoming connections</description> - </valueHelp> - <valueHelp> - <format>ipv6</format> - <description>IPv6 address to listen for incoming connections</description> - </valueHelp> - <multi/> - <constraint> - <validator name="ipv4-address"/> - <validator name="ipv6-address"/> - </constraint> - </properties> - </leafNode> + #include <include/listen-address.xml.i> <leafNode name="loglevel"> <properties> <help>Log level</help> diff --git a/interface-definitions/system-syslog.xml.in b/interface-definitions/system-syslog.xml.in index 194cdb851..b357bb036 100644 --- a/interface-definitions/system-syslog.xml.in +++ b/interface-definitions/system-syslog.xml.in @@ -382,6 +382,19 @@ </leafNode> </children> </tagNode> + <node name="format"> + <properties> + <help>Logging format</help> + </properties> + <children> + <leafNode name="octet-counted"> + <properties> + <help>Allows for the transmission of all characters inside a syslog message</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> </children> </tagNode> <node name="global"> diff --git a/interface-definitions/tftp-server.xml.in b/interface-definitions/tftp-server.xml.in index 2874b034c..abab71abd 100644 --- a/interface-definitions/tftp-server.xml.in +++ b/interface-definitions/tftp-server.xml.in @@ -22,34 +22,18 @@ </leafNode> <leafNode name="port"> <properties> - <help>Port for TFTP service</help> + <help>Port number used to listen for connections</help> <valueHelp> <format>1-65535</format> - <description>Numeric IP port (default: 69)</description> + <description>Numeric IP port</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> + <defaultValue>69</defaultValue> </leafNode> - <leafNode name="listen-address"> - <properties> - <help>Addresses for TFTP server to listen [REQUIRED]</help> - <valueHelp> - <format>ipv4</format> - <description>TFTP IPv4 listen address</description> - </valueHelp> - <valueHelp> - <format>ipv6</format> - <description>TFTP IPv6 listen address</description> - </valueHelp> - <multi/> - <constraint> - <validator name="ipv4-address"/> - <validator name="ipv6-address"/> - </constraint> - </properties> - </leafNode> + #include <include/listen-address.xml.i> </children> </node> </children> diff --git a/interface-definitions/vpn_openconnect.xml.in b/interface-definitions/vpn_openconnect.xml.in index 16fe660a9..203f85bbc 100644 --- a/interface-definitions/vpn_openconnect.xml.in +++ b/interface-definitions/vpn_openconnect.xml.in @@ -231,24 +231,7 @@ </leafNode> </children> </node> - <leafNode name="name-server"> - <properties> - <help>Domain Name Server (DNS) propagated to client</help> - <valueHelp> - <format>ipv4</format> - <description>Domain Name Server (DNS) IPv4 address</description> - </valueHelp> - <valueHelp> - <format>ipv6</format> - <description>Domain Name Server (DNS) IPv6 address</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - <validator name="ipv6-address"/> - </constraint> - <multi/> - </properties> - </leafNode> + #include <include/accel-name-server.xml.i> </children> </node> </children> diff --git a/op-mode-definitions/reset-mpls.xml b/op-mode-definitions/reset-mpls.xml new file mode 100644 index 000000000..4e5d37d5b --- /dev/null +++ b/op-mode-definitions/reset-mpls.xml @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="reset"> + <children> + <node name="mpls"> + <properties> + <help>Reset MPLS and related protocol commands</help> + </properties> + <children> + <node name="ldp"> + <properties> + <help>Reset LDP commands</help> + </properties> + <children> + <tagNode name="neighbor"> + <properties> + <help>Reset MPLS LDP neighbor/session</help> + <completionHelp> + <list><x.x.x.x> <h:h:h:h:h:h:h:h></list> + <script>/usr/bin/vtysh -c "show mpls ldp neighbor" | awk '{print $2}' | egrep -v "ID"</script> + </completionHelp> + </properties> + <command>/usr/bin/vtysh -c "clear mpls ldp neighbor $5"</command> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition>
\ No newline at end of file diff --git a/op-mode-definitions/show-protocols-bfd.xml b/op-mode-definitions/show-protocols-bfd.xml index 4483b39bf..3d9b67c67 100644 --- a/op-mode-definitions/show-protocols-bfd.xml +++ b/op-mode-definitions/show-protocols-bfd.xml @@ -40,6 +40,12 @@ </leafNode> </children> </tagNode> + <leafNode name="peers"> + <properties> + <help>Show Bidirectional Forwarding Detection (BFD) peers brief</help> + </properties> + <command>/usr/bin/vtysh -c "show bfd peers brief"</command> + </leafNode> </children> </node> </children> diff --git a/op-mode-definitions/show-protocols-static.xml b/op-mode-definitions/show-protocols-static.xml index 1211a7fe5..2693c4248 100644 --- a/op-mode-definitions/show-protocols-static.xml +++ b/op-mode-definitions/show-protocols-static.xml @@ -2,6 +2,23 @@ <interfaceDefinition> <node name="show"> <children> + <node name="arp"> + <properties> + <help>Show Address Resolution Protocol (ARP) information</help> + </properties> + <command>/usr/sbin/arp -e -n</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show Address Resolution Protocol (ARP) cache for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py -b</script> + </completionHelp> + </properties> + <command>/usr/sbin/arp -e -n -i "$6"</command> + </tagNode> + </children> + </node> <node name="protocols"> <children> <node name="static"> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index ce6d58693..62df3334c 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -18,12 +18,8 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion. """ import os -from copy import deepcopy - from vyos.util import vyos_dict_search from vyos.xml import defaults -from vyos.xml import is_tag -from vyos.xml import is_leaf from vyos import ConfigError def retrieve_config(path_hash, base_path, config): @@ -200,6 +196,7 @@ def is_member(conf, interface, intftype=None): """ ret_val = None intftypes = ['bonding', 'bridge'] + if intftype not in intftypes + [None]: raise ValueError(( f'unknown interface type "{intftype}" or it cannot ' @@ -211,19 +208,13 @@ def is_member(conf, interface, intftype=None): old_level = conf.get_level() conf.set_level([]) - for it in intftype: - base = ['interfaces', it] + for iftype in intftype: + base = ['interfaces', iftype] for intf in conf.list_nodes(base): - memberintf = base + [intf, 'member', 'interface'] - if is_tag(memberintf): - if interface in conf.list_nodes(memberintf): - ret_val = intf - break - elif is_leaf(memberintf): - if ( conf.exists(memberintf) and - interface in conf.return_values(memberintf) ): - ret_val = intf - break + member = base + [intf, 'member', 'interface', interface] + if conf.exists(member): + tmp = conf.get_config_dict(member, key_mangling=('-', '_'), get_first_key=True) + ret_val = {intf : tmp} old_level = conf.set_level(old_level) return ret_val @@ -265,11 +256,12 @@ def is_source_interface(conf, interface, intftype=None): def get_interface_dict(config, base, ifname=''): """ - Common utility function to retrieve and mandgle the interfaces available - in CLI configuration. All interfaces have a common base ground where the - value retrival is identical - so it can and should be reused + Common utility function to retrieve and mangle the interfaces configuration + from the CLI input nodes. All interfaces have a common base where value + retrival is identical. This function must be used whenever possible when + working on the interfaces node! - Will return a dictionary with the necessary interface configuration + Return a dictionary with the necessary interface config keys. """ if not ifname: # determine tagNode instance @@ -405,3 +397,70 @@ def get_interface_dict(config, base, ifname=''): # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) return dict + + +def get_accel_dict(config, base, chap_secrets): + """ + Common utility function to retrieve and mangle the Accel-PPP configuration + from different CLI input nodes. All Accel-PPP services have a common base + where value retrival is identical. This function must be used whenever + possible when working with Accel-PPP services! + + Return a dictionary with the necessary interface config keys. + """ + from vyos.util import get_half_cpus + from vyos.validate import is_ipv4 + + dict = config.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) + + # 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 vyos_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 + if vyos_dict_search('authentication.local_users.username', default_values): + del default_values['authentication']['local_users']['username'] + + dict = dict_merge(default_values, dict) + + # set CPUs cores to process requests + dict.update({'thread_count' : get_half_cpus()}) + # we need to store the path to the secrets file + dict.update({'chap_secrets_file' : chap_secrets}) + + # We can only have two IPv4 and three IPv6 nameservers - also they are + # configured in a different way in the configuration, this is why we split + # the configuration + if 'name_server' in dict: + ns_v4 = [] + ns_v6 = [] + for ns in dict['name_server']: + if is_ipv4(ns): ns_v4.append(ns) + else: ns_v6.append(ns) + + dict.update({'name_server_ipv4' : ns_v4, 'name_server_ipv6' : ns_v6}) + del dict['name_server'] + + # Add individual RADIUS server default values + if vyos_dict_search('authentication.radius.server', dict): + default_values = defaults(base + ['authentication', 'radius', 'server']) + + for server in vyos_dict_search('authentication.radius.server', dict): + dict['authentication']['radius']['server'][server] = dict_merge( + default_values, dict['authentication']['radius']['server'][server]) + + # Add individual local-user default values + if vyos_dict_search('authentication.local_users.username', dict): + default_values = defaults(base + ['authentication', 'local-users', 'username']) + + for username in vyos_dict_search('authentication.local_users.username', dict): + dict['authentication']['local_users']['username'][username] = dict_merge( + default_values, dict['authentication']['local_users']['username'][username]) + + return dict diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 944fc4294..422483663 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -22,6 +22,7 @@ # makes use of it! from vyos import ConfigError +from vyos.util import vyos_dict_search def verify_mtu(config): """ @@ -51,27 +52,26 @@ def verify_mtu_ipv6(config): configured on the interface. IPv6 requires a 1280 bytes MTU. """ from vyos.validate import is_ipv6 - from vyos.util import vyos_dict_search - # IPv6 minimum required link mtu - min_mtu = 1280 + if 'mtu' in config: + # IPv6 minimum required link mtu + min_mtu = 1280 + if int(config['mtu']) < min_mtu: + interface = config['ifname'] + error_msg = f'IPv6 address will be configured on interface "{interface}" ' \ + f'thus the minimum MTU requirement is {min_mtu}!' - if int(config['mtu']) < min_mtu: - interface = config['ifname'] - error_msg = f'IPv6 address will be configured on interface "{interface}" ' \ - f'thus the minimum MTU requirement is {min_mtu}!' + if not vyos_dict_search('ipv6.address.no_default_link_local', config): + raise ConfigError('link-local ' + error_msg) - if not vyos_dict_search('ipv6.address.no_default_link_local', config): - raise ConfigError('link-local ' + error_msg) + for address in (vyos_dict_search('address', config) or []): + if address in ['dhcpv6'] or is_ipv6(address): + raise ConfigError(error_msg) - for address in (vyos_dict_search('address', config) or []): - if address in ['dhcpv6'] or is_ipv6(address): + if vyos_dict_search('ipv6.address.autoconf', config): raise ConfigError(error_msg) - if vyos_dict_search('ipv6.address.autoconf', config): - raise ConfigError(error_msg) - - if vyos_dict_search('ipv6.address.eui64', config): - raise ConfigError(error_msg) + if vyos_dict_search('ipv6.address.eui64', config): + raise ConfigError(error_msg) def verify_vrf(config): @@ -204,3 +204,58 @@ def verify_vlan_config(config): verify_dhcpv6(vlan) verify_address(vlan) verify_vrf(vlan) + +def verify_accel_ppp_base_service(config): + """ + Common helper function which must be used by all Accel-PPP services based + on get_config_dict() + """ + # vertify auth settings + if vyos_dict_search('authentication.mode', config) == 'local': + if not vyos_dict_search('authentication.local_users', config): + raise ConfigError('PPPoE local auth mode requires local users to be configured!') + + for user in vyos_dict_search('authentication.local_users.username', config): + user_config = config['authentication']['local_users']['username'][user] + + if 'password' not in user_config: + raise ConfigError(f'Password required for local user "{user}"') + + if 'rate_limit' in user_config: + # if up/download is set, check that both have a value + if not {'upload', 'download'} <= set(user_config['rate_limit']): + raise ConfigError(f'User "{user}" has rate-limit configured for only one ' \ + 'direction but both upload and download must be given!') + + elif vyos_dict_search('authentication.mode', config) == 'radius': + if not vyos_dict_search('authentication.radius.server', config): + raise ConfigError('RADIUS authentication requires at least one server') + + for server in vyos_dict_search('authentication.radius.server', config): + radius_config = config['authentication']['radius']['server'][server] + if 'key' not in radius_config: + raise ConfigError(f'Missing RADIUS secret key for server "{server}"') + + if 'gateway_address' not in config: + raise ConfigError('PPPoE server requires gateway-address to be configured!') + + if 'name_server_ipv4' in config: + if len(config['name_server_ipv4']) > 2: + raise ConfigError('Not more then two IPv4 DNS name-servers ' \ + 'can be configured') + + if 'name_server_ipv6' in config: + if len(config['name_server_ipv6']) > 3: + raise ConfigError('Not more then three IPv6 DNS name-servers ' \ + 'can be configured') + + if 'client_ipv6_pool' in config: + ipv6_pool = config['client_ipv6_pool'] + if 'delegate' in ipv6_pool: + if 'prefix' not in ipv6_pool: + raise ConfigError('IPv6 "delegate" also requires "prefix" to be defined!') + + for delegate in ipv6_pool['delegate']: + if 'delegation_prefix' not in ipv6_pool['delegate'][delegate]: + raise ConfigError('delegation-prefix length required!') + diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index c133a56fc..bf78f8972 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -16,7 +16,6 @@ from netifaces import interfaces from vyos.ifconfig.interface import Interface -from vyos.ifconfig.stp import STP from vyos.validate import assert_boolean from vyos.validate import assert_positive from vyos.util import cmd @@ -234,25 +233,33 @@ class BridgeIf(Interface): if member in interfaces(): self.del_port(member) - STPBridgeIf = STP.enable(BridgeIf) tmp = vyos_dict_search('member.interface', config) if tmp: for interface, interface_config in tmp.items(): - # if we've come here we already verified the interface - # does not have an addresses configured so just flush - # any remaining ones - Interface(interface).flush_addrs() + # if interface does yet not exist bail out early and + # add it later + if interface not in interfaces(): + continue + + # Bridge lower "physical" interface + lower = Interface(interface) + + # If we've come that far we already verified the interface does + # not have any addresses configured by CLI so just flush any + # remaining ones + lower.flush_addrs() # enslave interface port to bridge self.add_port(interface) - tmp = STPBridgeIf(interface) # set bridge port path cost - value = interface_config.get('cost') - tmp.set_path_cost(value) + if 'cost' in interface_config: + value = interface_config.get('cost') + lower.set_path_cost(value) # set bridge port path priority - value = interface_config.get('priority') - tmp.set_path_priority(value) + if 'priority' in interface_config: + value = interface_config.get('priority') + lower.set_path_priority(value) # 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() diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index d200fc7a8..ae747e87c 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -147,6 +147,10 @@ class Interface(Control): 'validate': assert_boolean, 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_ignore', }, + 'ipv4_forwarding': { + 'validate': assert_boolean, + 'location': '/proc/sys/net/ipv4/conf/{ifname}/forwarding', + }, 'ipv6_accept_ra': { 'validate': lambda ara: assert_range(ara,0,3), 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra', @@ -163,6 +167,18 @@ class Interface(Control): 'validate': assert_positive, 'location': '/proc/sys/net/ipv6/conf/{ifname}/dad_transmits', }, + 'path_cost': { + # XXX: we should set a maximum + 'validate': assert_positive, + 'location': '/sys/class/net/{ifname}/brport/path_cost', + 'errormsg': '{ifname} is not a bridge port member' + }, + 'path_priority': { + # XXX: we should set a maximum + 'validate': assert_positive, + 'location': '/sys/class/net/{ifname}/brport/priority', + 'errormsg': '{ifname} is not a bridge port member' + }, 'proxy_arp': { 'validate': assert_boolean, 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp', @@ -461,6 +477,12 @@ class Interface(Control): """ return self.set_interface('arp_ignore', arp_ignore) + def set_ipv4_forwarding(self, forwarding): + """ + Configure IPv4 forwarding. + """ + return self.set_interface('ipv4_forwarding', forwarding) + def set_ipv6_accept_ra(self, accept_ra): """ Accept Router Advertisements; autoconfigure using them. @@ -618,6 +640,28 @@ class Interface(Control): self._admin_state_down_cnt += 1 return self.set_interface('admin_state', state) + def set_path_cost(self, cost): + """ + Set interface path cost, only relevant for STP enabled interfaces + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_path_cost(4) + """ + self.set_interface('path_cost', cost) + + def set_path_priority(self, priority): + """ + Set interface path priority, only relevant for STP enabled interfaces + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_path_priority(4) + """ + self.set_interface('path_priority', priority) + def set_proxy_arp(self, enable): """ Set per interface proxy ARP configuration @@ -799,24 +843,27 @@ class Interface(Control): # flush all addresses self._cmd(f'ip addr flush dev "{self.ifname}"') - def add_to_bridge(self, br): + def add_to_bridge(self, bridge_dict): """ Adds the interface to the bridge with the passed port config. Returns False if bridge doesn't exist. """ - # check if the bridge exists (on boot it doesn't) - if br not in Section.interfaces('bridge'): - return False - + # drop all interface addresses first self.flush_addrs() - # add interface to bridge - use Section.klass to get BridgeIf class - Section.klass(br)(br, create=False).add_port(self.ifname) - # TODO: port config (STP) + for bridge, bridge_config in bridge_dict.items(): + # add interface to bridge - use Section.klass to get BridgeIf class + Section.klass(bridge)(bridge, create=True).add_port(self.ifname) - return True + # set bridge port path cost + if 'cost' in bridge_config: + self.set_path_cost(bridge_config['cost']) + + # set bridge port path priority + if 'priority' in bridge_config: + self.set_path_cost(bridge_config['priority']) def set_dhcp(self, enable): """ @@ -974,6 +1021,11 @@ class Interface(Control): value = '1' if (tmp != None) else '0' self.set_proxy_arp_pvlan(value) + # IPv4 forwarding + tmp = vyos_dict_search('ip.disable_forwarding', config) + value = '0' if (tmp != None) else '1' + self.set_ipv4_forwarding(value) + # IPv6 forwarding tmp = vyos_dict_search('ipv6.disable_forwarding', config) value = '0' if (tmp != None) else '1' @@ -1032,8 +1084,8 @@ class Interface(Control): # re-add ourselves to any bridge we might have fallen out of if 'is_bridge_member' in config: - bridge = config.get('is_bridge_member') - self.add_to_bridge(bridge) + bridge_dict = config.get('is_bridge_member') + self.add_to_bridge(bridge_dict) # remove no longer required 802.1ad (Q-in-Q VLANs) ifname = config['ifname'] diff --git a/python/vyos/ifconfig/stp.py b/python/vyos/ifconfig/stp.py deleted file mode 100644 index 5e83206c2..000000000 --- a/python/vyos/ifconfig/stp.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2019 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 -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see <http://www.gnu.org/licenses/>. - - -from vyos.ifconfig.interface import Interface - -from vyos.validate import assert_positive - - -class STP: - """ - A spanning-tree capable interface. This applies only to bridge port member - interfaces! - """ - - @classmethod - def enable (cls, adaptee): - adaptee._sysfs_set = {**adaptee._sysfs_set, **cls._sysfs_set} - adaptee.set_path_cost = cls.set_path_cost - adaptee.set_path_priority = cls.set_path_priority - return adaptee - - _sysfs_set = { - 'path_cost': { - # XXX: we should set a maximum - 'validate': assert_positive, - 'location': '/sys/class/net/{ifname}/brport/path_cost', - 'errormsg': '{ifname} is not a bridge port member' - }, - 'path_priority': { - # XXX: we should set a maximum - 'validate': assert_positive, - 'location': '/sys/class/net/{ifname}/brport/priority', - 'errormsg': '{ifname} is not a bridge port member' - }, - } - - def set_path_cost(self, cost): - """ - Set interface path cost, only relevant for STP enabled interfaces - - Example: - - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_path_cost(4) - """ - self.set_interface('path_cost', cost) - - def set_path_priority(self, priority): - """ - Set interface path priority, only relevant for STP enabled interfaces - - Example: - - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_path_priority(4) - """ - self.set_interface('path_priority', priority) diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py index cf401b0d8..56cbf1dd4 100644 --- a/smoketest/scripts/cli/base_accel_ppp_test.py +++ b/smoketest/scripts/cli/base_accel_ppp_test.py @@ -44,6 +44,9 @@ class BasicAccelPPPTest: def set(self, path): self.session.set(self._base_path + path) + def delete(self, path): + self.session.delete(self._base_path + path) + def basic_config(self): # PPPoE local auth mode requires local users to be configured! self.set(['authentication', 'local-users', 'username', 'vyos', 'password', 'vyos']) @@ -117,6 +120,20 @@ class BasicAccelPPPTest: # Check for running process self.assertTrue(process_named_running(self._process_name)) + # Check local-users default value(s) + self.delete(['authentication', 'local-users', 'username', user, 'static-ip']) + # commit changes + self.session.commit() + + # check local users + tmp = cmd(f'sudo cat {self._chap_secrets}') + regex = f'{user}\s+\*\s+{password}\s+\*\s+{download}/{upload}' + tmp = re.findall(regex, tmp) + self.assertTrue(tmp) + + # Check for running process + self.assertTrue(process_named_running(self._process_name)) + def test_authentication_radius(self): """ Test configuration of RADIUS authentication for PPPoE server """ self.basic_config() diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 047c19dd0..c6bb5bd1a 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -228,7 +228,7 @@ class BasicInterfaceTest: self._mtu_test(vif) def test_ip_options(self): - """ test IP options like arp """ + """ Test interface base IPv4 options """ if not self._test_ip: return None @@ -241,6 +241,7 @@ class BasicInterfaceTest: # Options self.session.set(path + ['ip', 'arp-cache-timeout', arp_tmo]) self.session.set(path + ['ip', 'disable-arp-filter']) + self.session.set(path + ['ip', 'disable-forwarding']) self.session.set(path + ['ip', 'enable-arp-accept']) self.session.set(path + ['ip', 'enable-arp-announce']) self.session.set(path + ['ip', 'enable-arp-ignore']) @@ -266,6 +267,9 @@ class BasicInterfaceTest: tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/arp_ignore') self.assertEqual('1', tmp) + tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/forwarding') + self.assertEqual('0', tmp) + tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/proxy_arp') self.assertEqual('1', tmp) @@ -274,3 +278,27 @@ class BasicInterfaceTest: tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/rp_filter') self.assertEqual('2', tmp) + + def test_ipv6_options(self): + """ Test interface base IPv6 options """ + if not self._test_ipv6: + return None + + for interface in self._interfaces: + dad_transmits = '10' + path = self._base_path + [interface] + for option in self._options.get(interface, []): + self.session.set(path + option.split()) + + # Options + self.session.set(path + ['ipv6', 'disable-forwarding']) + self.session.set(path + ['ipv6', 'dup-addr-detect-transmits', dad_transmits]) + + self.session.commit() + + for interface in self._interfaces: + tmp = read_file(f'/proc/sys/net/ipv6/conf/{interface}/forwarding') + self.assertEqual('0', tmp) + + tmp = read_file(f'/proc/sys/net/ipv6/conf/{interface}/dad_transmits') + self.assertEqual(dad_transmits, tmp) diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py new file mode 100755 index 000000000..0ac91c170 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -0,0 +1,97 @@ +#!/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 +from vyos.util import read_file + +PROCESS_NAME = 'openvpn' + +base_path = ['interfaces', 'openvpn'] +ca_cert = '/config/auth/ovpn_test_ca.crt' +ssl_cert = '/config/auth/ovpn_test_server.crt' +ssl_key = '/config/auth/ovpn_test_server.key' + +class TestInterfacesOpenVPN(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_client(self): + """ Basic OpenVPN client test """ + interface = 'vtun10' + remote_host = '192.0.2.1' + remote_port = '1194' + protocol = 'udp' + path = base_path + [interface] + + self.session.set(path + ['device-type', 'tun']) + self.session.set(path + ['encryption', 'cipher', 'aes256']) + self.session.set(path + ['hash', 'sha1']) + self.session.set(path + ['mode', 'client']) + self.session.set(path + ['persistent-tunnel']) + self.session.set(path + ['protocol', protocol]) + self.session.set(path + ['remote-host', remote_host]) + self.session.set(path + ['remote-port', remote_port]) + 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]) + + self.session.commit() + + config_file = f'/run/openvpn/{interface}.conf' + config = read_file(config_file) + + self.assertIn(f'dev {interface}', config) + self.assertIn('dev-type tun', config) + self.assertIn('persist-key', config) + self.assertIn(f'proto {protocol}', config) + self.assertIn(f'rport {remote_port}', config) + self.assertIn(f'remote {remote_host}', config) + self.assertIn('persist-tun', config) + + + self.assertTrue(process_named_running(PROCESS_NAME)) + +if __name__ == '__main__': + # Our SSL certificates need a subject ... + subject = '/C=DE/ST=BY/O=VyOS/localityName=Cloud/commonName=vyos/' \ + 'organizationalUnitName=VyOS/emailAddress=maintainers@vyos.io/' + + if not os.path.isfile(ssl_key) and not os.path.isfile(ssl_cert) and not os.path.isfile(ca_cert): + # Generate mandatory SSL certificate + tmp = f'openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 '\ + f'-keyout {ssl_key} -out {ssl_cert} -subj {subject}' + cmd(tmp) + + # Generate "CA" + tmp = f'openssl req -new -x509 -key {ssl_key} -out {ca_cert} '\ + f'-subj {subject}' + cmd(tmp) + + for file in [ca_cert, ssl_cert, ssl_key]: + cmd(f'sudo chown openvpn:openvpn {file}') + + unittest.main() diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py index b06fa239d..b5bde743b 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -19,13 +19,14 @@ import jmespath import json import unittest -from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError from vyos.util import cmd +from vyos.util import vyos_dict_search base_path = ['nat'] -source_path = base_path + ['source'] - -snat_pattern = 'nftables[?rule].rule[?chain].{chain: chain, comment: comment, address: { network: expr[].match.right.prefix.addr | [0], prefix: expr[].match.right.prefix.len | [0]}}' +src_path = base_path + ['source'] +dst_path = base_path + ['destination'] class TestNAT(unittest.TestCase): def setUp(self): @@ -38,37 +39,126 @@ class TestNAT(unittest.TestCase): self.session.delete(base_path) self.session.commit() - def test_source_nat(self): - """ Configure and validate source NAT rule(s) """ - - network = '192.168.0.0/16' - self.session.set(source_path + ['rule', '1', 'destination', 'address', network]) - self.session.set(source_path + ['rule', '1', 'exclude']) + def test_snat(self): + """ Test source NAT (SNAT) rules """ + + rules = ['100', '110', '120', '130', '200', '210', '220', '230'] + outbound_iface_100 = 'eth0' + outbound_iface_200 = 'eth1' + for rule in rules: + network = f'192.168.{rule}.0/24' + # depending of rule order we check either for source address for NAT + # or configured destination address for NAT + if int(rule) < 200: + self.session.set(src_path + ['rule', rule, 'source', 'address', network]) + self.session.set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_100]) + self.session.set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) + else: + self.session.set(src_path + ['rule', rule, 'destination', 'address', network]) + self.session.set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_200]) + self.session.set(src_path + ['rule', rule, 'exclude']) - # check validate() - outbound-interface must be defined - with self.assertRaises(ConfigSessionError): - self.session.commit() - - self.session.set(source_path + ['rule', '1', 'outbound-interface', 'any']) self.session.commit() tmp = cmd('sudo nft -j list table nat') - nftable_json = json.loads(tmp) - condensed_json = jmespath.search(snat_pattern, nftable_json)[0] - - self.assertEqual(condensed_json['comment'], 'DST-NAT-1') - self.assertEqual(condensed_json['address']['network'], network.split('/')[0]) - self.assertEqual(str(condensed_json['address']['prefix']), network.split('/')[1]) + data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) + + for idx in range(0, len(data_json)): + rule = str(rules[idx]) + data = data_json[idx] + network = f'192.168.{rule}.0/24' + + self.assertEqual(data['chain'], 'POSTROUTING') + self.assertEqual(data['comment'], f'SRC-NAT-{rule}') + self.assertEqual(data['family'], 'ip') + self.assertEqual(data['table'], 'nat') + + iface = vyos_dict_search('match.right', data['expr'][0]) + direction = vyos_dict_search('match.left.payload.field', data['expr'][1]) + address = vyos_dict_search('match.right.prefix.addr', data['expr'][1]) + mask = vyos_dict_search('match.right.prefix.len', data['expr'][1]) + + if int(rule) < 200: + self.assertEqual(direction, 'saddr') + self.assertEqual(iface, outbound_iface_100) + # check for masquerade keyword + self.assertIn('masquerade', data['expr'][3]) + else: + self.assertEqual(direction, 'daddr') + self.assertEqual(iface, outbound_iface_200) + # check for return keyword due to 'exclude' + self.assertIn('return', data['expr'][3]) + + self.assertEqual(f'{address}/{mask}', network) + + def test_dnat(self): + """ Test destination NAT (DNAT) rules """ + + rules = ['100', '110', '120', '130', '200', '210', '220', '230'] + inbound_iface_100 = 'eth0' + inbound_iface_200 = 'eth1' + inbound_proto_100 = 'udp' + inbound_proto_200 = 'tcp' + + for rule in rules: + port = f'10{rule}' + self.session.set(dst_path + ['rule', rule, 'source', 'port', port]) + self.session.set(dst_path + ['rule', rule, 'translation', 'address', '192.0.2.1']) + self.session.set(dst_path + ['rule', rule, 'translation', 'port', port]) + if int(rule) < 200: + self.session.set(dst_path + ['rule', rule, 'protocol', inbound_proto_100]) + self.session.set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_100]) + else: + self.session.set(dst_path + ['rule', rule, 'protocol', inbound_proto_200]) + self.session.set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_200]) + self.session.commit() - def test_validation(self): + tmp = cmd('sudo nft -j list table nat') + data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) + + for idx in range(0, len(data_json)): + rule = str(rules[idx]) + data = data_json[idx] + port = int(f'10{rule}') + + self.assertEqual(data['chain'], 'PREROUTING') + self.assertEqual(data['comment'].split()[0], f'DST-NAT-{rule}') + self.assertEqual(data['family'], 'ip') + self.assertEqual(data['table'], 'nat') + + iface = vyos_dict_search('match.right', data['expr'][0]) + direction = vyos_dict_search('match.left.payload.field', data['expr'][1]) + protocol = vyos_dict_search('match.left.payload.protocol', data['expr'][1]) + dnat_addr = vyos_dict_search('dnat.addr', data['expr'][3]) + dnat_port = vyos_dict_search('dnat.port', data['expr'][3]) + + self.assertEqual(direction, 'sport') + self.assertEqual(dnat_addr, '192.0.2.1') + self.assertEqual(dnat_port, port) + if int(rule) < 200: + self.assertEqual(iface, inbound_iface_100) + self.assertEqual(protocol, inbound_proto_100) + else: + self.assertEqual(iface, inbound_iface_200) + + + def test_validation_logic(self): """ T2813: Ensure translation address is specified """ - self.session.set(source_path + ['rule', '100', 'outbound-interface', 'eth0']) + rule = '5' + self.session.set(src_path + ['rule', rule, 'source', 'address', '192.0.2.0/24']) + + # check validate() - outbound-interface must be defined + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(src_path + ['rule', rule, 'outbound-interface', 'eth0']) # check validate() - translation address not specified with self.assertRaises(ConfigSessionError): self.session.commit() + self.session.set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) + self.session.commit() if __name__ == '__main__': unittest.main() diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py index 8db002b57..f0c71e2de 100755 --- a/smoketest/scripts/cli/test_service_pppoe-server.py +++ b/smoketest/scripts/cli/test_service_pppoe-server.py @@ -47,7 +47,7 @@ class TestServicePPPoEServer(BasicAccelPPPTest.BaseTest): mtu = '1492' # validate some common values in the configuration - for tmp in ['log_syslog', 'pppoe', 'chap-secrets', 'ippool', + for tmp in ['log_syslog', 'pppoe', 'ippool', 'auth_mschap_v2', 'auth_mschap_v1', 'auth_chap_md5', 'auth_pap', 'shaper']: # Settings without values provide None diff --git a/smoketest/scripts/cli/test_service_tftp-server.py b/smoketest/scripts/cli/test_service_tftp-server.py new file mode 100755 index 000000000..92333392a --- /dev/null +++ b/smoketest/scripts/cli/test_service_tftp-server.py @@ -0,0 +1,105 @@ +#!/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 re +import os +import unittest + +from psutil import process_iter + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.util import read_file +from vyos.util import process_named_running +from vyos.validate import is_ipv6 + +PROCESS_NAME = 'in.tftpd' +base_path = ['service', 'tftp-server'] +dummy_if_path = ['interfaces', 'dummy', 'dum69'] +address_ipv4 = '192.0.2.1' +address_ipv6 = '2001:db8::1' + +class TestServiceTFTPD(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + self.session.set(dummy_if_path + ['address', address_ipv4 + '/32']) + self.session.set(dummy_if_path + ['address', address_ipv6 + '/128']) + + def tearDown(self): + self.session.delete(base_path) + self.session.delete(dummy_if_path) + self.session.commit() + del self.session + + def test_01_tftpd_single(self): + directory = '/tmp' + port = '69' # default port + + self.session.set(base_path + ['allow-upload']) + self.session.set(base_path + ['directory', directory]) + self.session.set(base_path + ['listen-address', address_ipv4]) + + # commit changes + self.session.commit() + + config = read_file('/etc/default/tftpd0') + # verify listen IP address + self.assertIn(f'{address_ipv4}:{port} -4', config) + # verify directory + self.assertIn(directory, config) + # verify upload + self.assertIn('--create --umask 000', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_02_tftpd_multi(self): + directory = '/tmp' + address = [address_ipv4, address_ipv6] + port = '70' + + self.session.set(base_path + ['directory', directory]) + for addr in address: + self.session.set(base_path + ['listen-address', addr]) + self.session.set(base_path + ['port', port]) + + # commit changes + self.session.commit() + + for idx in range(0, len(address)): + config = read_file(f'/etc/default/tftpd{idx}') + addr = address[idx] + + # verify listen IP address + if is_ipv6(addr): + addr = f'[{addr}]' + self.assertIn(f'{addr}:{port} -6', config) + else: + self.assertIn(f'{addr}:{port} -4', config) + + # verify directory + self.assertIn(directory, config) + + # Check for running processes - one process is spawned per listen + # IP address, wheter it's IPv4 or IPv6 + count = 0 + for p in process_iter(): + if PROCESS_NAME in p.name(): + count += 1 + self.assertEqual(count, len(address)) + +if __name__ == '__main__': + unittest.main() diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py index 2a7c64870..4f62b62d5 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_system_ntp.py @@ -70,10 +70,6 @@ class TestSystemNTP(unittest.TestCase): def test_ntp_clients(self): """ Test the allowed-networks statement """ - listen_address = ['127.0.0.1', '::1'] - for listen in listen_address: - self.session.set(base_path + ['listen-address', listen]) - networks = ['192.0.2.0/24', '2001:db8:1000::/64'] for network in networks: self.session.set(base_path + ['allow-clients', 'address', network]) @@ -99,9 +95,7 @@ class TestSystemNTP(unittest.TestCase): # Check listen address tmp = get_config_value('interface') - test = ['ignore wildcard'] - for listen in listen_address: - test.append(f'listen {listen}') + test = ['ignore wildcard', 'listen 127.0.0.1', 'listen ::1'] self.assertEqual(tmp, test) # Check for running process diff --git a/smoketest/scripts/cli/test_vpn_sstp.py b/smoketest/scripts/cli/test_vpn_sstp.py index 83be4c248..9babb83dc 100755 --- a/smoketest/scripts/cli/test_vpn_sstp.py +++ b/smoketest/scripts/cli/test_vpn_sstp.py @@ -19,7 +19,6 @@ import unittest from base_accel_ppp_test import BasicAccelPPPTest from vyos.util import cmd -process_name = 'accel-pppd' ca_cert = '/tmp/ca.crt' ssl_cert = '/tmp/server.crt' ssl_key = '/tmp/server.key' diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py index 4a47b9246..78daeb6be 100755 --- a/src/conf_mode/bcast_relay.py +++ b/src/conf_mode/bcast_relay.py @@ -78,7 +78,8 @@ def generate(relay): continue config['instance'] = instance - render(config_file_base + instance, 'bcast-relay/udp-broadcast-relay.tmpl', config) + render(config_file_base + instance, 'bcast-relay/udp-broadcast-relay.tmpl', + config, trim_blocks=True) return None diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 4ce4cada1..1777d4db7 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -65,6 +65,7 @@ def get_config(config=None): config = { 'name': network, 'disabled': False, + 'common': {}, 'subnet': [] } @@ -72,6 +73,31 @@ def get_config(config=None): if conf.exists(['disable']): config['disabled'] = True + # Common options shared among subnets. These can be overridden if + # the same option is specified on a per-subnet or per-host + # basis. These are the only options that can be handed out to + # stateless clients via an information-request message. + if conf.exists(['common-options']): + conf.set_level(base + ['shared-network-name', network, 'common-options']) + + # How often stateless clients should refresh their information. This is + # mostly taken as a hint by clients, and only if they request it. + # (if not specified, the server does not supply this to the client) + if conf.exists(['info-refresh-time']): + config['common']['info_refresh_time'] = conf.return_value(['info-refresh-time']) + + # The domain-search option specifies a 'search list' of Domain Names to be used + # by the client to locate not-fully-qualified domain names. + if conf.exists(['domain-search']): + config['common']['domain_search'] = conf.return_values(['domain-search']) + + # Specifies a list of Domain Name System name servers available to the client. + # Servers should be listed in order of preference. + if conf.exists(['name-server']): + config['common']['dns_server'] = conf.return_values(['name-server']) + + conf.set_level(base + ['shared-network-name', network]) + # check for multiple subnet configurations in a shared network if conf.exists(['subnet']): for net in conf.list_nodes(['subnet']): diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 5101c1e79..2187b3c73 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -26,6 +26,7 @@ from vyos.util import chown from vyos.util import vyos_dict_search from vyos.template import render from vyos.xml import defaults +from vyos.validate import is_ipv6 from vyos import ConfigError from vyos import airbag @@ -65,6 +66,21 @@ def get_config(config=None): if conf.exists(base_nameservers_dhcp): dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)}) + # Split the source_address property into separate IPv4 and IPv6 lists + # NOTE: In future versions of pdns-recursor (> 4.4.0), this logic can be removed + # as both IPv4 and IPv6 addresses can be specified in a single setting. + source_address_v4 = [] + source_address_v6 = [] + + for source_address in dns['source_address']: + if is_ipv6(source_address): + source_address_v6.append(source_address) + else: + source_address_v4.append(source_address) + + dns.update({'source_address_v4': source_address_v4}) + dns.update({'source_address_v6': source_address_v6}) + return dns def verify(dns): diff --git a/src/conf_mode/intel_qat.py b/src/conf_mode/intel_qat.py index 86dbccaf0..ab98cbc03 100755 --- a/src/conf_mode/intel_qat.py +++ b/src/conf_mode/intel_qat.py @@ -67,7 +67,7 @@ def verify(qat): output, err = popen('lspci -nn', decode='utf-8') if not err: data = re.findall( - '(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)', output) + '(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)|(8086:1f18)', output) # If QAT devices found if not data: raise ConfigError('No QAT acceleration device found') diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 9763620ac..ea9bd54d4 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -109,7 +109,7 @@ def get_config(config=None): # Check if member interface is already member of a bond tmp = is_member(conf, interface, 'bonding') - if tmp and tmp != bond['ifname']: + if tmp and bond['ifname'] not in tmp: interface_config.update({'is_bond_member' : tmp}) # Check if member interface is used as source-interface on another interface @@ -162,11 +162,11 @@ def verify(bond): raise ConfigError(error_msg + 'it does not exist!') if 'is_bridge_member' in interface_config: - tmp = interface_config['is_bridge_member'] + tmp = next(iter(interface_config['is_bridge_member'])) raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') if 'is_bond_member' in interface_config: - tmp = interface_config['is_bond_member'] + tmp = next(iter(interface_config['is_bond_member'])) raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') if 'is_source_interface' in interface_config: diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 485decb17..4aeb8fc67 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -24,6 +24,7 @@ from vyos.configdict import get_interface_dict from vyos.configdict import node_changed from vyos.configdict import is_member from vyos.configdict import is_source_interface +from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf @@ -69,25 +70,26 @@ def get_config(config=None): # the default dictionary is not properly paged into the dict (see T2665) # thus we will ammend it ourself default_member_values = defaults(base + ['member', 'interface']) - for interface, interface_config in bridge['member']['interface'].items(): - interface_config.update(default_member_values) + for interface in bridge['member']['interface']: + bridge['member']['interface'][interface] = dict_merge( + default_member_values, bridge['member']['interface'][interface]) # Check if member interface is already member of another bridge tmp = is_member(conf, interface, 'bridge') - if tmp and tmp != bridge['ifname']: - interface_config.update({'is_bridge_member' : tmp}) + if tmp and bridge['ifname'] not in tmp: + bridge['member']['interface'][interface].update({'is_bridge_member' : tmp}) # Check if member interface is already member of a bond tmp = is_member(conf, interface, 'bonding') - if tmp: interface_config.update({'is_bond_member' : tmp}) + if tmp: bridge['member']['interface'][interface].update({'is_bond_member' : tmp}) # Check if member interface is used as source-interface on another interface tmp = is_source_interface(conf, interface) - if tmp: interface_config.update({'is_source_interface' : tmp}) + if tmp: bridge['member']['interface'][interface].update({'is_source_interface' : tmp}) # Bridge members must not have an assigned address tmp = has_address_configured(conf, interface) - if tmp: interface_config.update({'has_address' : ''}) + if tmp: bridge['member']['interface'][interface].update({'has_address' : ''}) return bridge @@ -105,15 +107,12 @@ def verify(bridge): if interface == 'lo': raise ConfigError('Loopback interface "lo" can not be added to a bridge') - if interface not in interfaces(): - raise ConfigError(error_msg + 'it does not exist!') - if 'is_bridge_member' in interface_config: - tmp = interface_config['is_bridge_member'] + tmp = next(iter(interface_config['is_bridge_member'])) raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') if 'is_bond_member' in interface_config: - tmp = interface_config['is_bond_member'] + tmp = next(iter(interface_config['is_bond_member'])) raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') if 'is_source_interface' in interface_config: diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 518dbdc0e..f2b580c6f 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -208,7 +208,8 @@ def get_config(config=None): openvpn['auth_user_pass_file'] = f"/run/openvpn/{openvpn['intf']}.pw" # check if interface is member of a bridge - openvpn['is_bridge_member'] = is_member(conf, openvpn['intf'], 'bridge') + tmp = is_member(conf, openvpn['intf'], 'bridge') + if tmp: openvpn['is_bridge_member'] = next(iter(tmp)) # Check if interface instance has been removed if not conf.exists('interfaces openvpn ' + openvpn['intf']): diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index f1d885b15..5561514bd 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -462,7 +462,8 @@ def get_config(config=None): options['tunnel'] = {} # check for bridges - options['bridge'] = is_member(config, ifname, 'bridge') + tmp = is_member(config, ifname, 'bridge') + if tmp: options['bridge'] = next(iter(tmp)) options['interfaces'] = interfaces() for name in ct: diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index ad8aee168..c1770771e 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -76,6 +76,14 @@ def get_config(config=None): base = ['interfaces', 'wireless'] wifi = get_interface_dict(conf, base) + + # Cleanup "delete" default values when required user selectable values are + # not defined at all + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if not (vyos_dict_search('security.wpa.passphrase', tmp) or + vyos_dict_search('security.wpa.radius', tmp)): + del wifi['security']['wpa'] + # 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 vyos_dict_search('security.wpa.radius.server.port', wifi): diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index e515490d0..904d219e2 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -43,7 +43,12 @@ def get_config(config=None): 'd_transp_ipv4' : None, 'd_transp_ipv6' : None, 'hello_holdtime' : None, - 'hello_interval' : None + 'hello_interval' : None, + 'ses_ipv4_hold' : None, + 'ses_ipv6_hold' : None, + 'export_ipv4_exp' : False, + 'export_ipv6_exp' : False + }, 'ldp' : { 'interfaces' : [], @@ -51,7 +56,12 @@ def get_config(config=None): 'd_transp_ipv4' : None, 'd_transp_ipv6' : None, 'hello_holdtime' : None, - 'hello_interval' : None + 'hello_interval' : None, + 'ses_ipv4_hold' : None, + 'ses_ipv6_hold' : None, + 'export_ipv4_exp' : False, + 'export_ipv6_exp' : False + } } if not (conf.exists('protocols mpls') or conf.exists_effective('protocols mpls')): @@ -82,6 +92,20 @@ def get_config(config=None): if conf.exists('discovery hello-interval'): mpls_conf['ldp']['hello_interval'] = conf.return_value('discovery hello-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') @@ -96,6 +120,20 @@ def get_config(config=None): 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 interfaces if conf.exists_effective('interface'): mpls_conf['old_ldp']['interfaces'] = conf.return_effective_values('interface') diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 445311391..a520120f8 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -19,13 +19,11 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.validate import is_ipv4 +from vyos.configdict import get_accel_dict +from vyos.configverify import verify_accel_ppp_base_service from vyos.template import render from vyos.util import call -from vyos.util import get_half_cpus from vyos.util import vyos_dict_search -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -42,108 +40,22 @@ def get_config(config=None): if not conf.exists(base): return None - pppoe = 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) - - # 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 vyos_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 - if vyos_dict_search('authentication.local_users.username', default_values): - del default_values['authentication']['local_users']['username'] - - pppoe = dict_merge(default_values, pppoe) - - # set CPUs cores to process requests - pppoe.update({'thread_count' : get_half_cpus()}) - # we need to store the path to the secrets file - pppoe.update({'chap_secrets_file' : pppoe_chap_secrets}) - - # We can only have two IPv4 and three IPv6 nameservers - also they are - # configured in a different way in the configuration, this is why we split - # the configuration - if 'name_server' in pppoe: - ns_v4 = [] - ns_v6 = [] - for ns in pppoe['name_server']: - if is_ipv4(ns): ns_v4.append(ns) - else: ns_v6.append(ns) - - pppoe.update({'name_server_ipv4' : ns_v4, 'name_server_ipv6' : ns_v6}) - del pppoe['name_server'] - - # Add individual RADIUS server default values - if vyos_dict_search('authentication.radius.server', pppoe): - default_values = defaults(base + ['authentication', 'radius', 'server']) - - for server in vyos_dict_search('authentication.radius.server', pppoe): - pppoe['authentication']['radius']['server'][server] = dict_merge( - default_values, pppoe['authentication']['radius']['server'][server]) - - # Add individual local-user default values - if vyos_dict_search('authentication.local_users.username', pppoe): - default_values = defaults(base + ['authentication', 'local_users', 'username']) - - for username in vyos_dict_search('authentication.local_users.username', pppoe): - pppoe['authentication']['local_users']['username'][username] = dict_merge( - default_values, pppoe['authentication']['local_users']['username'][username]) - + # retrieve common dictionary keys + pppoe = get_accel_dict(conf, base, pppoe_chap_secrets) return pppoe - def verify(pppoe): if not pppoe: return None - # vertify auth settings - if vyos_dict_search('authentication.mode', pppoe) == 'local': - if not vyos_dict_search('authentication.local_users', pppoe): - raise ConfigError('PPPoE local auth mode requires local users to be configured!') - - for user in vyos_dict_search('authentication.local_users.username', pppoe): - user_config = pppoe['authentication']['local_users']['username'][user] - - if 'password' not in user_config: - raise ConfigError(f'Password required for local user "{user}"') - - if 'rate_limit' in user_config: - # if up/download is set, check that both have a value - if not {'upload', 'download'} <= set(user_config['rate_limit']): - raise ConfigError(f'User "{user}" has rate-limit configured for only one ' \ - 'direction but both upload and download must be given!') - - elif vyos_dict_search('authentication.mode', pppoe) == 'radius': - if not vyos_dict_search('authentication.radius.server', pppoe): - raise ConfigError('RADIUS authentication requires at least one server') - - for server in vyos_dict_search('authentication.radius.server', pppoe): - radius_config = pppoe['authentication']['radius']['server'][server] - if 'key' not in radius_config: - raise ConfigError(f'Missing RADIUS secret key for server "{server}"') + verify_accel_ppp_base_service(pppoe) if 'wins_server' in pppoe and len(pppoe['wins_server']) > 2: raise ConfigError('Not more then two IPv4 WINS name-servers can be configured') - if 'name_server_ipv4' in pppoe: - if len(pppoe['name_server_ipv4']) > 2: - raise ConfigError('Not more then two IPv4 DNS name-servers ' \ - 'can be configured') - - if 'name_server_ipv6' in pppoe: - if len(pppoe['name_server_ipv6']) > 3: - raise ConfigError('Not more then three IPv6 DNS name-servers ' \ - 'can be configured') - if 'interface' not in pppoe: raise ConfigError('At least one listen interface must be defined!') - if 'gateway_address' not in pppoe: - raise ConfigError('PPPoE server requires gateway-address to be configured!') - # local ippool and gateway settings config checks if not (vyos_dict_search('client_ip_pool.subnet', pppoe) or (vyos_dict_search('client_ip_pool.start', pppoe) and @@ -164,7 +76,8 @@ def generate(pppoe): render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe, trim_blocks=True) if vyos_dict_search('authentication.mode', pppoe) == 'local': - render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.pppoe.tmpl', pppoe, trim_blocks=True, permission=0o640) + render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl', + pppoe, trim_blocks=True, permission=0o640) else: if os.path.exists(pppoe_chap_secrets): os.unlink(pppoe_chap_secrets) diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 2aca199f9..2c0bbd4f7 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -233,8 +233,8 @@ def generate(login): env = os.environ.copy() env['vyos_libexec_dir'] = '/usr/libexec/vyos' - call("/opt/vyatta/sbin/my_set system login user '{name}' " - "authentication plaintext-password '{password_plaintext}'" + call("/opt/vyatta/sbin/my_delete system login user '{name}' " + "authentication plaintext-password" .format(**user), env=env) call("/opt/vyatta/sbin/my_set system login user '{name}' " diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index d29109c41..b1daf7a82 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -146,6 +146,12 @@ def get_config(config=None): config_data['hosts'][rhost][ 'port'] = c.return_value(['host', rhost, 'port']) + # set system syslog host x.x.x.x format octet-counted + if c.exists('host ' + rhost + ' format octet-counted'): + config_data['hosts'][rhost]['oct_count'] = True + else: + config_data['hosts'][rhost]['oct_count'] = False + # set system syslog user if c.exists('user'): usrs = c.list_nodes('user') diff --git a/src/conf_mode/system-timezone.py b/src/conf_mode/system-timezone.py index 4d9f017a6..3d98ba774 100755 --- a/src/conf_mode/system-timezone.py +++ b/src/conf_mode/system-timezone.py @@ -48,6 +48,7 @@ def generate(tz): def apply(tz): call('/usr/bin/timedatectl set-timezone {}'.format(tz['name'])) + call('systemctl restart rsyslog') if __name__ == '__main__': try: diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index ad5ee9c33..cac95afe2 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -23,64 +23,52 @@ from glob import glob from sys import exit from vyos.config import Config -from vyos.validate import is_ipv4, is_addr_assigned -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 chmod_755 +from vyos.validate import is_ipv4 +from vyos.validate import is_addr_assigned +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() config_file = r'/etc/default/tftpd' -default_config_data = { - 'directory': '', - 'allow_upload': False, - 'port': '69', - 'listen': [] -} - def get_config(config=None): - tftpd = deepcopy(default_config_data) if config: conf = config else: conf = Config() + base = ['service', 'tftp-server'] if not conf.exists(base): return None - else: - conf.set_level(base) - - if conf.exists(['directory']): - tftpd['directory'] = conf.return_value(['directory']) - - if conf.exists(['allow-upload']): - tftpd['allow_upload'] = True - - if conf.exists(['port']): - tftpd['port'] = conf.return_value(['port']) - - if conf.exists(['listen-address']): - tftpd['listen'] = conf.return_values(['listen-address']) + tftpd = 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) + tftpd = dict_merge(default_values, tftpd) return tftpd def verify(tftpd): # bail out early - looks like removal from running config - if tftpd is None: + if not tftpd: return None # Configuring allowed clients without a server makes no sense - if not tftpd['directory']: + if 'directory' not in tftpd: raise ConfigError('TFTP root directory must be configured!') - if not tftpd['listen']: + if 'listen_address' not in tftpd: raise ConfigError('TFTP server listen address must be configured!') - for addr in tftpd['listen']: - if not is_addr_assigned(addr): - print('WARNING: TFTP server listen address {0} not assigned to any interface!'.format(addr)) + for address in tftpd['listen_address']: + if not is_addr_assigned(address): + print(f'WARNING: TFTP server listen address "{address}" not ' \ + 'assigned to any interface!') return None @@ -95,23 +83,23 @@ def generate(tftpd): return None idx = 0 - for listen in tftpd['listen']: + for address in tftpd['listen_address']: config = deepcopy(tftpd) - if is_ipv4(listen): - config['listen'] = [listen + ":" + tftpd['port'] + " -4"] + port = tftpd['port'] + if is_ipv4(address): + config['listen_address'] = f'{address}:{port} -4' else: - config['listen'] = ["[" + listen + "]" + tftpd['port'] + " -6"] + config['listen_address'] = f'[{address}]:{port} -6' file = config_file + str(idx) - render(file, 'tftp-server/default.tmpl', config) - + render(file, 'tftp-server/default.tmpl', config, trim_blocks=True) idx = idx + 1 return None def apply(tftpd): # stop all services first - then we will decide - call('systemctl stop tftpd@{0..20}.service') + call('systemctl stop tftpd@*.service') # bail out early - e.g. service deletion if tftpd is None: @@ -120,7 +108,7 @@ def apply(tftpd): tftp_root = tftpd['directory'] if not os.path.exists(tftp_root): os.makedirs(tftp_root) - os.chmod(tftp_root, stat.S_IRUSR|stat.S_IWUSR|stat.S_IXUSR|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH) + chmod_755(tftp_root) # get UNIX uid for user 'tftp' tftp_uid = pwd.getpwnam('tftp').pw_uid @@ -135,8 +123,8 @@ def apply(tftpd): os.chown(tftp_root, tftp_uid, tftp_gid) idx = 0 - for listen in tftpd['listen']: - call('systemctl restart tftpd@{0}.service'.format(idx)) + for address in tftpd['listen_address']: + call(f'systemctl restart tftpd@{idx}.service') idx = idx + 1 return None diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index 3eece1922..2597ba42f 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -16,340 +16,66 @@ import os -from time import sleep from sys import exit -from copy import deepcopy -from stat import S_IRUSR, S_IWUSR, S_IRGRP from vyos.config import Config +from vyos.configdict import get_accel_dict +from vyos.configverify import verify_accel_ppp_base_service from vyos.template import render -from vyos.util import call, run, get_half_cpus -from vyos.validate import is_ipv4 +from vyos.util import call +from vyos.util import vyos_dict_search from vyos import ConfigError - from vyos import airbag airbag.enable() sstp_conf = '/run/accel-pppd/sstp.conf' sstp_chap_secrets = '/run/accel-pppd/sstp.chap-secrets' -default_config_data = { - 'local_users' : [], - 'auth_mode' : 'local', - 'auth_proto' : ['auth_mschap_v2'], - 'chap_secrets_file': sstp_chap_secrets, # used in Jinja2 template - 'client_ip_pool' : [], - 'client_ipv6_pool': [], - 'client_ipv6_delegate_prefix': [], - 'client_gateway': '', - 'dnsv4' : [], - 'dnsv6' : [], - 'radius_server' : [], - 'radius_acct_tmo' : '3', - 'radius_max_try' : '3', - 'radius_timeout' : '3', - 'radius_nas_id' : '', - 'radius_nas_ip' : '', - 'radius_source_address' : '', - 'radius_shaper_attr' : '', - 'radius_shaper_vendor': '', - 'radius_dynamic_author' : '', - 'ssl_ca' : '', - 'ssl_cert' : '', - 'ssl_key' : '', - 'mtu' : '', - 'ppp_mppe' : 'prefer', - 'ppp_echo_failure' : '', - 'ppp_echo_interval' : '', - 'ppp_echo_timeout' : '', - 'thread_cnt' : get_half_cpus() -} - def get_config(config=None): - sstp = deepcopy(default_config_data) - base_path = ['vpn', 'sstp'] if config: conf = config else: conf = Config() - if not conf.exists(base_path): + base = ['vpn', 'sstp'] + if not conf.exists(base): return None - conf.set_level(base_path) - - if conf.exists(['authentication', 'mode']): - sstp['auth_mode'] = conf.return_value(['authentication', 'mode']) - - # - # local auth - if conf.exists(['authentication', 'local-users']): - for username in conf.list_nodes(['authentication', 'local-users', 'username']): - user = { - 'name' : username, - 'password' : '', - 'state' : 'enabled', - 'ip' : '*', - 'upload' : None, - 'download' : None - } - - conf.set_level(base_path + ['authentication', 'local-users', 'username', username]) - - if conf.exists(['password']): - user['password'] = conf.return_value(['password']) - - if conf.exists(['disable']): - user['state'] = 'disable' - - if conf.exists(['static-ip']): - user['ip'] = conf.return_value(['static-ip']) - - if conf.exists(['rate-limit', 'download']): - user['download'] = conf.return_value(['rate-limit', 'download']) - - if conf.exists(['rate-limit', 'upload']): - user['upload'] = conf.return_value(['rate-limit', 'upload']) - - sstp['local_users'].append(user) - - # - # RADIUS auth and settings - conf.set_level(base_path + ['authentication', 'radius']) - if conf.exists(['server']): - for server in conf.list_nodes(['server']): - radius = { - 'server' : server, - 'key' : '', - 'fail_time' : 0, - 'port' : '1812', - 'acct_port' : '1813' - } - - conf.set_level(base_path + ['authentication', 'radius', 'server', server]) - - if conf.exists(['fail-time']): - radius['fail_time'] = conf.return_value(['fail-time']) - - if conf.exists(['port']): - radius['port'] = conf.return_value(['port']) - - if conf.exists(['acct-port']): - radius['acct_port'] = conf.return_value(['acct-port']) - - if conf.exists(['key']): - radius['key'] = conf.return_value(['key']) - - if not conf.exists(['disable']): - sstp['radius_server'].append(radius) - - # - # advanced radius-setting - conf.set_level(base_path + ['authentication', 'radius']) - - if conf.exists(['acct-timeout']): - sstp['radius_acct_tmo'] = conf.return_value(['acct-timeout']) - - if conf.exists(['max-try']): - sstp['radius_max_try'] = conf.return_value(['max-try']) - - if conf.exists(['timeout']): - sstp['radius_timeout'] = conf.return_value(['timeout']) - - if conf.exists(['nas-identifier']): - sstp['radius_nas_id'] = conf.return_value(['nas-identifier']) - - if conf.exists(['nas-ip-address']): - sstp['radius_nas_ip'] = conf.return_value(['nas-ip-address']) - - if conf.exists(['source-address']): - sstp['radius_source_address'] = conf.return_value(['source-address']) - - # Dynamic Authorization Extensions (DOA)/Change Of Authentication (COA) - if conf.exists(['dynamic-author']): - dae = { - 'port' : '1700', - 'server' : '', - 'key' : '' - } - - if conf.exists(['dynamic-author', 'server']): - dae['server'] = conf.return_value(['dynamic-author', 'server']) - - if conf.exists(['dynamic-author', 'port']): - dae['port'] = conf.return_value(['dynamic-author', 'port']) - - if conf.exists(['dynamic-author', 'key']): - dae['key'] = conf.return_value(['dynamic-author', 'key']) - - sstp['radius_dynamic_author'] = dae - - if conf.exists(['rate-limit', 'enable']): - sstp['radius_shaper_attr'] = 'Filter-Id' - c_attr = ['rate-limit', 'enable', 'attribute'] - if conf.exists(c_attr): - sstp['radius_shaper_attr'] = conf.return_value(c_attr) - - c_vendor = ['rate-limit', 'enable', 'vendor'] - if conf.exists(c_vendor): - sstp['radius_shaper_vendor'] = conf.return_value(c_vendor) - - # - # authentication protocols - conf.set_level(base_path + ['authentication']) - if conf.exists(['protocols']): - # clear default list content, now populate with actual CLI values - sstp['auth_proto'] = [] - auth_mods = { - 'pap': 'auth_pap', - 'chap': 'auth_chap_md5', - 'mschap': 'auth_mschap_v1', - 'mschap-v2': 'auth_mschap_v2' - } - - for proto in conf.return_values(['protocols']): - sstp['auth_proto'].append(auth_mods[proto]) - - # - # read in SSL certs - conf.set_level(base_path + ['ssl']) - if conf.exists(['ca-cert-file']): - sstp['ssl_ca'] = conf.return_value(['ca-cert-file']) - - if conf.exists(['cert-file']): - sstp['ssl_cert'] = conf.return_value(['cert-file']) - - if conf.exists(['key-file']): - sstp['ssl_key'] = conf.return_value(['key-file']) - - - # - # read in client IPv4 pool - conf.set_level(base_path + ['client-ip-pool']) - if conf.exists(['subnet']): - sstp['client_ip_pool'] = conf.return_values(['subnet']) - - # - # read in client IPv6 pool - conf.set_level(base_path + ['client-ipv6-pool']) - if conf.exists(['prefix']): - for prefix in conf.list_nodes(['prefix']): - tmp = { - 'prefix': prefix, - 'mask': '64' - } - - if conf.exists(['prefix', prefix, 'mask']): - tmp['mask'] = conf.return_value(['prefix', prefix, 'mask']) - - sstp['client_ipv6_pool'].append(tmp) - - if conf.exists(['delegate']): - for prefix in conf.list_nodes(['delegate']): - tmp = { - 'prefix': prefix, - 'mask': '' - } - - if conf.exists(['delegate', prefix, 'delegation-prefix']): - tmp['mask'] = conf.return_value(['delegate', prefix, 'delegation-prefix']) - - sstp['client_ipv6_delegate_prefix'].append(tmp) - - # - # read in network settings - conf.set_level(base_path) - if conf.exists(['gateway-address']): - sstp['client_gateway'] = conf.return_value(['gateway-address']) - - if conf.exists(['name-server']): - for name_server in conf.return_values(['name-server']): - if is_ipv4(name_server): - sstp['dnsv4'].append(name_server) - else: - sstp['dnsv6'].append(name_server) - - if conf.exists(['mtu']): - sstp['mtu'] = conf.return_value(['mtu']) - - # - # read in PPP stuff - conf.set_level(base_path + ['ppp-options']) - if conf.exists('mppe'): - sstp['ppp_mppe'] = conf.return_value(['mppe']) - - if conf.exists(['lcp-echo-failure']): - sstp['ppp_echo_failure'] = conf.return_value(['lcp-echo-failure']) - - if conf.exists(['lcp-echo-interval']): - sstp['ppp_echo_interval'] = conf.return_value(['lcp-echo-interval']) - - if conf.exists(['lcp-echo-timeout']): - sstp['ppp_echo_timeout'] = conf.return_value(['lcp-echo-timeout']) - + # retrieve common dictionary keys + sstp = get_accel_dict(conf, base, sstp_chap_secrets) return sstp - def verify(sstp): - if sstp is None: + if not sstp: return None - # vertify auth settings - if sstp['auth_mode'] == 'local': - if not sstp['local_users']: - raise ConfigError('SSTP local auth mode requires local users to be configured!') - - for user in sstp['local_users']: - username = user['name'] - if not user['password']: - raise ConfigError(f'Password required for local user "{username}"') - - # if up/download is set, check that both have a value - if user['upload'] and not user['download']: - raise ConfigError(f'Download speed value required for local user "{username}"') - - if user['download'] and not user['upload']: - raise ConfigError(f'Upload speed value required for local user "{username}"') - - if not sstp['client_ip_pool']: - raise ConfigError('Client IP subnet required') - - if not sstp['client_gateway']: - raise ConfigError('Client gateway IP address required') - - if len(sstp['dnsv4']) > 2: - raise ConfigError('Not more then two IPv4 DNS name-servers can be configured') + verify_accel_ppp_base_service(sstp) - # check ipv6 - if sstp['client_ipv6_delegate_prefix'] and not sstp['client_ipv6_pool']: - raise ConfigError('IPv6 prefix delegation requires client-ipv6-pool prefix') + if not sstp['client_ip_pool']: + raise ConfigError('Client IP subnet required') - for prefix in sstp['client_ipv6_delegate_prefix']: - if not prefix['mask']: - raise ConfigError('Delegation-prefix required for individual delegated networks') - - if not sstp['ssl_ca'] or not sstp['ssl_cert'] or not sstp['ssl_key']: - raise ConfigError('One or more SSL certificates missing') - - if not os.path.exists(sstp['ssl_ca']): - file = sstp['ssl_ca'] - raise ConfigError(f'SSL CA certificate file "{file}" does not exist') - - if not os.path.exists(sstp['ssl_cert']): - file = sstp['ssl_cert'] - raise ConfigError(f'SSL public key file "{file}" does not exist') - - if not os.path.exists(sstp['ssl_key']): - file = sstp['ssl_key'] - raise ConfigError(f'SSL private key file "{file}" does not exist') + # + # SSL certificate checks + # + tmp = vyos_dict_search('ssl.ca_cert_file', sstp) + if not tmp: + raise ConfigError(f'SSL CA certificate file required!') + else: + if not os.path.isfile(tmp): + raise ConfigError(f'SSL CA certificate "{tmp}" does not exist!') - if sstp['auth_mode'] == 'radius': - if len(sstp['radius_server']) == 0: - raise ConfigError('RADIUS authentication requires at least one server') + tmp = vyos_dict_search('ssl.cert_file', sstp) + if not tmp: + raise ConfigError(f'SSL public key file required!') + else: + if not os.path.isfile(tmp): + raise ConfigError(f'SSL public key "{tmp}" does not exist!') - for radius in sstp['radius_server']: - if not radius['key']: - server = radius['server'] - raise ConfigError(f'Missing RADIUS secret key for server "{ server }"') + tmp = vyos_dict_search('ssl.key_file', sstp) + if not tmp: + raise ConfigError(f'SSL private key file required!') + else: + if not os.path.isfile(tmp): + raise ConfigError(f'SSL private key "{tmp}" does not exist!') def generate(sstp): if not sstp: @@ -358,9 +84,9 @@ def generate(sstp): # 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) - if sstp['local_users']: - render(sstp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', sstp, trim_blocks=True) - os.chmod(sstp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP) + if vyos_dict_search('authentication.mode', sstp) == 'local': + render(sstp_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl', + sstp, trim_blocks=True, permission=0o640) else: if os.path.exists(sstp_chap_secrets): os.unlink(sstp_chap_secrets) diff --git a/src/migration-scripts/pppoe-server/1-to-2 b/src/migration-scripts/pppoe-server/1-to-2 index 7cae3b5bc..902efb86b 100755 --- a/src/migration-scripts/pppoe-server/1-to-2 +++ b/src/migration-scripts/pppoe-server/1-to-2 @@ -14,7 +14,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# Convert "service pppoe-server interface ethX" to: "service pppoe-server interface ethX {}" +# change mppe node to a leaf node with value prefer + +import os from sys import argv, exit from vyos.configtree import ConfigTree @@ -28,21 +30,32 @@ file_name = argv[1] with open(file_name, 'r') as f: config_file = f.read() -ctree = ConfigTree(config_file) -cbase = ['service', 'pppoe-server','interface'] - -if not ctree.exists(cbase): +config = ConfigTree(config_file) +base = ['service', 'pppoe-server'] +if not config.exists(base): + # Nothing to do exit(0) else: - nics = ctree.return_values(cbase) - # convert leafNode to a tagNode - ctree.set(cbase) - ctree.set_tag(cbase) - for nic in nics: - ctree.set(cbase + [nic]) + mppe_base = base + ['ppp-options', 'mppe'] + if config.exists(mppe_base): + # get current values + tmp = config.list_nodes(mppe_base) + # drop node(s) first ... + config.delete(mppe_base) + + print(tmp) + # set new value based on preference + if 'require' in tmp: + config.set(mppe_base, value='require') + elif 'prefer' in tmp: + config.set(mppe_base, value='prefer') + elif 'deny' in tmp: + config.set(mppe_base, value='deny') try: - open(file_name,'w').write(ctree.to_string()) + 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/pppoe-server/2-to-3 b/src/migration-scripts/pppoe-server/2-to-3 index 5f9730a41..7cae3b5bc 100755 --- a/src/migration-scripts/pppoe-server/2-to-3 +++ b/src/migration-scripts/pppoe-server/2-to-3 @@ -14,9 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# - remove primary/secondary identifier from nameserver - -import os +# Convert "service pppoe-server interface ethX" to: "service pppoe-server interface ethX {}" from sys import argv, exit from vyos.configtree import ConfigTree @@ -30,112 +28,21 @@ file_name = argv[1] with open(file_name, 'r') as f: config_file = f.read() -config = ConfigTree(config_file) -base = ['service', 'pppoe-server'] -if not config.exists(base): - # Nothing to do +ctree = ConfigTree(config_file) +cbase = ['service', 'pppoe-server','interface'] + +if not ctree.exists(cbase): exit(0) else: - - # Migrate IPv4 DNS servers - dns_base = base + ['dns-servers'] - if config.exists(dns_base): - for server in ['server-1', 'server-2']: - if config.exists(dns_base + [server]): - dns = config.return_value(dns_base + [server]) - config.set(base + ['name-server'], value=dns, replace=False) - - config.delete(dns_base) - - # Migrate IPv6 DNS servers - dns_base = base + ['dnsv6-servers'] - if config.exists(dns_base): - for server in ['server-1', 'server-2', 'server-3']: - if config.exists(dns_base + [server]): - dns = config.return_value(dns_base + [server]) - config.set(base + ['name-server'], value=dns, replace=False) - - config.delete(dns_base) - - # Migrate IPv4 WINS servers - wins_base = base + ['wins-servers'] - if config.exists(wins_base): - for server in ['server-1', 'server-2']: - if config.exists(wins_base + [server]): - wins = config.return_value(wins_base + [server]) - config.set(base + ['wins-server'], value=wins, replace=False) - - config.delete(wins_base) - - # Migrate radius-settings node to RADIUS and use this as base for the - # later migration of the RADIUS servers - this will save a lot of code - radius_settings = base + ['authentication', 'radius-settings'] - if config.exists(radius_settings): - config.rename(radius_settings, 'radius') - - # Migrate RADIUS dynamic author / change of authorisation server - dae_old = base + ['authentication', 'radius', 'dae-server'] - if config.exists(dae_old): - config.rename(dae_old, 'dynamic-author') - dae_new = base + ['authentication', 'radius', 'dynamic-author'] - - if config.exists(dae_new + ['ip-address']): - config.rename(dae_new + ['ip-address'], 'server') - - if config.exists(dae_new + ['secret']): - config.rename(dae_new + ['secret'], 'key') - - # Migrate RADIUS server - radius_server = base + ['authentication', 'radius-server'] - if config.exists(radius_server): - new_base = base + ['authentication', 'radius', 'server'] - config.set(new_base) - config.set_tag(new_base) - for server in config.list_nodes(radius_server): - old_base = radius_server + [server] - config.copy(old_base, new_base + [server]) - - # migrate key - if config.exists(new_base + [server, 'secret']): - config.rename(new_base + [server, 'secret'], 'key') - - # remove old req-limit node - if config.exists(new_base + [server, 'req-limit']): - config.delete(new_base + [server, 'req-limit']) - - config.delete(radius_server) - - # Migrate IPv6 prefixes - ipv6_base = base + ['client-ipv6-pool'] - if config.exists(ipv6_base + ['prefix']): - prefix_old = config.return_values(ipv6_base + ['prefix']) - # delete old prefix CLI nodes - config.delete(ipv6_base + ['prefix']) - # create ned prefix tag node - config.set(ipv6_base + ['prefix']) - config.set_tag(ipv6_base + ['prefix']) - - for p in prefix_old: - prefix = p.split(',')[0] - mask = p.split(',')[1] - config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask) - - if config.exists(ipv6_base + ['delegate-prefix']): - prefix_old = config.return_values(ipv6_base + ['delegate-prefix']) - # delete old delegate prefix CLI nodes - config.delete(ipv6_base + ['delegate-prefix']) - # create ned delegation tag node - config.set(ipv6_base + ['delegate']) - config.set_tag(ipv6_base + ['delegate']) - - for p in prefix_old: - prefix = p.split(',')[0] - mask = p.split(',')[1] - config.set(ipv6_base + ['delegate', prefix, 'delegation-prefix'], value=mask) + nics = ctree.return_values(cbase) + # convert leafNode to a tagNode + ctree.set(cbase) + ctree.set_tag(cbase) + for nic in nics: + ctree.set(cbase + [nic]) try: - with open(file_name, 'w') as f: - f.write(config.to_string()) + open(file_name,'w').write(ctree.to_string()) except OSError as e: print("Failed to save the modified config: {}".format(e)) exit(1) diff --git a/src/migration-scripts/pppoe-server/3-to-4 b/src/migration-scripts/pppoe-server/3-to-4 index 6709d5f86..5f9730a41 100755 --- a/src/migration-scripts/pppoe-server/3-to-4 +++ b/src/migration-scripts/pppoe-server/3-to-4 @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# change mppe node to a leaf node with value prefer +# - remove primary/secondary identifier from nameserver import os @@ -36,12 +36,102 @@ if not config.exists(base): # Nothing to do exit(0) else: - mppe_base = base + ['ppp-options', 'mppe'] - if config.exists(mppe_base): - # drop node first ... - config.delete(mppe_base) - # ... and set new default - config.set(mppe_base, value='prefer') + + # Migrate IPv4 DNS servers + dns_base = base + ['dns-servers'] + if config.exists(dns_base): + for server in ['server-1', 'server-2']: + if config.exists(dns_base + [server]): + dns = config.return_value(dns_base + [server]) + config.set(base + ['name-server'], value=dns, replace=False) + + config.delete(dns_base) + + # Migrate IPv6 DNS servers + dns_base = base + ['dnsv6-servers'] + if config.exists(dns_base): + for server in ['server-1', 'server-2', 'server-3']: + if config.exists(dns_base + [server]): + dns = config.return_value(dns_base + [server]) + config.set(base + ['name-server'], value=dns, replace=False) + + config.delete(dns_base) + + # Migrate IPv4 WINS servers + wins_base = base + ['wins-servers'] + if config.exists(wins_base): + for server in ['server-1', 'server-2']: + if config.exists(wins_base + [server]): + wins = config.return_value(wins_base + [server]) + config.set(base + ['wins-server'], value=wins, replace=False) + + config.delete(wins_base) + + # Migrate radius-settings node to RADIUS and use this as base for the + # later migration of the RADIUS servers - this will save a lot of code + radius_settings = base + ['authentication', 'radius-settings'] + if config.exists(radius_settings): + config.rename(radius_settings, 'radius') + + # Migrate RADIUS dynamic author / change of authorisation server + dae_old = base + ['authentication', 'radius', 'dae-server'] + if config.exists(dae_old): + config.rename(dae_old, 'dynamic-author') + dae_new = base + ['authentication', 'radius', 'dynamic-author'] + + if config.exists(dae_new + ['ip-address']): + config.rename(dae_new + ['ip-address'], 'server') + + if config.exists(dae_new + ['secret']): + config.rename(dae_new + ['secret'], 'key') + + # Migrate RADIUS server + radius_server = base + ['authentication', 'radius-server'] + if config.exists(radius_server): + new_base = base + ['authentication', 'radius', 'server'] + config.set(new_base) + config.set_tag(new_base) + for server in config.list_nodes(radius_server): + old_base = radius_server + [server] + config.copy(old_base, new_base + [server]) + + # migrate key + if config.exists(new_base + [server, 'secret']): + config.rename(new_base + [server, 'secret'], 'key') + + # remove old req-limit node + if config.exists(new_base + [server, 'req-limit']): + config.delete(new_base + [server, 'req-limit']) + + config.delete(radius_server) + + # Migrate IPv6 prefixes + ipv6_base = base + ['client-ipv6-pool'] + if config.exists(ipv6_base + ['prefix']): + prefix_old = config.return_values(ipv6_base + ['prefix']) + # delete old prefix CLI nodes + config.delete(ipv6_base + ['prefix']) + # create ned prefix tag node + config.set(ipv6_base + ['prefix']) + config.set_tag(ipv6_base + ['prefix']) + + for p in prefix_old: + prefix = p.split(',')[0] + mask = p.split(',')[1] + config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask) + + if config.exists(ipv6_base + ['delegate-prefix']): + prefix_old = config.return_values(ipv6_base + ['delegate-prefix']) + # delete old delegate prefix CLI nodes + config.delete(ipv6_base + ['delegate-prefix']) + # create ned delegation tag node + config.set(ipv6_base + ['delegate']) + config.set_tag(ipv6_base + ['delegate']) + + for p in prefix_old: + prefix = p.split(',')[0] + mask = p.split(',')[1] + config.set(ipv6_base + ['delegate', prefix, 'delegation-prefix'], value=mask) try: with open(file_name, 'w') as f: diff --git a/src/services/vyos-configd b/src/services/vyos-configd index 671a89036..5b1ab1f1f 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -136,7 +136,7 @@ def initialization(socket): session_string = '' # check first for resent init msg, in case of client timeout while True: - msg = socket.recv().decode() + msg = socket.recv().decode("utf-8", "ignore") try: message = json.loads(msg) if message["type"] == "init": @@ -149,10 +149,10 @@ def initialization(socket): active_string = msg resp = "active" socket.send(resp.encode()) - session_string = socket.recv().decode() + session_string = socket.recv().decode("utf-8", "ignore") resp = "session" socket.send(resp.encode()) - pid_string = socket.recv().decode() + pid_string = socket.recv().decode("utf-8", "ignore") resp = "pid" socket.send(resp.encode()) |