summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/configd-include.json1
-rw-r--r--data/templates/frr/bgp.frr.tmpl175
-rw-r--r--data/templates/frr/ldpd.frr.tmpl33
-rw-r--r--data/templates/https/nginx.default.tmpl2
-rw-r--r--data/templates/openvpn/client.conf.tmpl48
-rw-r--r--data/templates/openvpn/server.conf.tmpl357
-rw-r--r--debian/control2
-rwxr-xr-xdebian/rules4
-rw-r--r--debian/vyos-1x-smoketest.install2
-rw-r--r--interface-definitions/dhcp-server.xml.in1
-rw-r--r--interface-definitions/include/accel-radius-additions-disable-accounting.xlm.in7
-rw-r--r--interface-definitions/include/accel-radius-additions.xml.i1
-rw-r--r--interface-definitions/include/bgp-neighbor-afi-ipv4-unicast.xml.i1
-rw-r--r--interface-definitions/include/bgp-neighbor-afi-ipv6-unicast.xml.i7
-rw-r--r--interface-definitions/include/interface-mtu-1200-16000.xml.i16
-rw-r--r--interface-definitions/include/interface-mtu-1200-9000.xml.i16
-rw-r--r--interface-definitions/include/interface-mtu-1450-16000.xml.i16
-rw-r--r--interface-definitions/include/interface-mtu-1450-9000.xml.i16
-rw-r--r--interface-definitions/include/interface-mtu-64-8024.xml.i2
-rw-r--r--interface-definitions/include/interface-mtu-68-1500.xml.i2
-rw-r--r--interface-definitions/include/interface-mtu-68-16000.xml.i16
-rw-r--r--interface-definitions/include/interface-mtu-68-9000.xml.i16
-rw-r--r--interface-definitions/include/vif-s.xml.i4
-rw-r--r--interface-definitions/include/vif.xml.i2
-rw-r--r--interface-definitions/interfaces-bonding.xml.in2
-rw-r--r--interface-definitions/interfaces-ethernet.xml.in2
-rw-r--r--interface-definitions/interfaces-geneve.xml.in2
-rw-r--r--interface-definitions/interfaces-l2tpv3.xml.in2
-rw-r--r--interface-definitions/interfaces-macsec.xml.in2
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in37
-rw-r--r--interface-definitions/interfaces-pseudo-ethernet.xml.in2
-rw-r--r--interface-definitions/interfaces-vxlan.xml.in2
-rw-r--r--interface-definitions/interfaces-wireguard.xml.in2
-rw-r--r--interface-definitions/interfaces-wirelessmodem.xml.in2
-rw-r--r--interface-definitions/protocols-bgp.xml.in27
-rw-r--r--interface-definitions/protocols-mpls.xml.in32
-rw-r--r--interface-definitions/system-ipv6.xml.in2
-rw-r--r--interface-definitions/vpn_l2tp.xml.in1
-rw-r--r--python/vyos/configdict.py25
-rw-r--r--python/vyos/configsession.py5
-rw-r--r--python/vyos/configverify.py62
-rw-r--r--python/vyos/ifconfig/bond.py8
-rw-r--r--python/vyos/ifconfig/bridge.py8
-rw-r--r--python/vyos/ifconfig/ethernet.py12
-rw-r--r--python/vyos/ifconfig/interface.py36
-rw-r--r--python/vyos/ifconfig/tunnel.py2
-rw-r--r--python/vyos/ifconfig/vtun.py44
-rw-r--r--python/vyos/template.py75
-rw-r--r--python/vyos/util.py73
-rw-r--r--python/vyos/validate.py72
-rwxr-xr-xsmoketest/bin/vyos-configtest82
-rw-r--r--smoketest/configs/pppoe-client62
-rw-r--r--smoketest/configs/pppoe-server94
-rw-r--r--smoketest/scripts/cli/base_accel_ppp_test.py20
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py33
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_openvpn.py374
-rwxr-xr-xsmoketest/scripts/cli/test_nat.py20
-rwxr-xr-xsmoketest/scripts/cli/test_service_ssh.py32
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py4
-rwxr-xr-xsrc/conf_mode/intel_qat.py8
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py6
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py6
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py1128
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py14
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py11
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py80
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py14
-rwxr-xr-xsrc/conf_mode/ssh.py7
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py8
-rwxr-xr-xsrc/conf_mode/vpn_pptp.py3
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py10
-rwxr-xr-xsrc/helpers/vyos-load-config.py46
-rwxr-xr-xsrc/services/vyos-hostsd2
-rwxr-xr-xsrc/services/vyos-http-api-server2
-rw-r--r--src/tests/helper.py3
-rw-r--r--src/tests/test_config_parser.py4
-rw-r--r--src/tests/test_configverify.py38
-rw-r--r--src/tests/test_dict_search.py (renamed from src/tests/test_vyos_dict_search.py)32
-rwxr-xr-xsrc/tests/test_find_device_file.py35
-rw-r--r--src/tests/test_jinja_filters.py69
-rw-r--r--src/tests/test_util.py6
-rw-r--r--src/tests/test_validate.py64
-rw-r--r--vyos-configtest0
83 files changed, 2017 insertions, 1586 deletions
diff --git a/data/configd-include.json b/data/configd-include.json
index 2711a29b8..95aef65ad 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -6,7 +6,6 @@
"dynamic_dns.py",
"firewall_options.py",
"host_name.py",
-"http-api.py",
"https.py",
"igmp_proxy.py",
"intel_qat.py",
diff --git a/data/templates/frr/bgp.frr.tmpl b/data/templates/frr/bgp.frr.tmpl
index d011a1e85..d0857ac2c 100644
--- a/data/templates/frr/bgp.frr.tmpl
+++ b/data/templates/frr/bgp.frr.tmpl
@@ -14,10 +14,9 @@ router bgp {{ asn }}
{%- if type == "ipv4_unicast" %}
!
address-family ipv4 unicast
-{# need to check #}
{%- if 'aggregate_address' in bgp_afi[type] %}
{%- for ip in bgp_afi[type].aggregate_address %}
-{%- if ( ('as_set' and 'summary_only') in bgp_afi[type].aggregate_address[ip] ) %}
+{%- if ( ('as_set' in bgp_afi[type].aggregate_address[ip]) and ('summary_only' in bgp_afi[type].aggregate_address[ip] ) ) %}
aggregate-address {{ ip }} as-set summary-only
{%- elif 'as_set' in bgp_afi[type].aggregate_address[ip] %}
aggregate-address {{ ip }} as-set
@@ -28,23 +27,20 @@ router bgp {{ asn }}
{%- endif %}
{%- endfor %}
{%- endif %}
-{# END aggregate address#}
-{#- redistribute #}
-{# need to check. dont work.
- 'metric' and 'route_map' match also only 'route_map'
- 'table' parameter also include in protocol, its not what I want #}
+{#- END aggregate address ipv4 #}
+
+{#- redistribute afi ipv4 #}
{%- if 'redistribute' in bgp_afi[type] %}
-{%- if 'table' in bgp_afi[type].redistribute %}
- redistribute table {{bgp_afi[type].redistribute.table}}
-{%- endif %}
{%- for protocol in bgp_afi[type].redistribute %}
-{%- if ( ('metric' and 'route_map') in bgp_afi[type].redistribute[protocol] ) %}
+{%- if ( ('route_map' in bgp_afi[type].redistribute[protocol]) and ('metric' in bgp_afi[type].redistribute[protocol] ) ) %}
redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} route-map {{bgp_afi[type].redistribute[protocol].route_map}}
{%- elif 'metric' in bgp_afi[type].redistribute[protocol] %}
- redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}}
+ redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}}
{%- elif 'route_map' in bgp_afi[type].redistribute[protocol] %}
redistribute {{protocol}} route-map {{bgp_afi[type].redistribute[protocol].route_map}}
-{%- else %}
+{%- elif 'table' in bgp_afi[type].redistribute %}
+ redistribute table {{bgp_afi[type].redistribute.table}}
+{%- else %}
redistribute {{protocol}}
{%- endif %}
{%- endfor %}
@@ -65,7 +61,7 @@ router bgp {{ asn }}
address-family ipv6 unicast
{%- if 'aggregate_address' in bgp_afi[type] %}
{%- for ip in bgp_afi[type].aggregate_address %}
-{%- if ( ('as_set' and 'summary_only') in bgp_afi[type].aggregate_address[ip] ) %}
+{%- if ( ('as_set' in bgp_afi[type].aggregate_address[ip]) and ('summary_only' in bgp_afi[type].aggregate_address[ip] ) ) %}
aggregate-address {{ ip }} as-set summary-only
{%- elif 'as_set' in bgp_afi[type].aggregate_address[ip] %}
aggregate-address {{ ip }} as-set
@@ -76,22 +72,20 @@ router bgp {{ asn }}
{%- endif %}
{%- endfor %}
{%- endif %}
-{# END aggregate address#}
+{#- END aggregate address ipv6 #}
-{#- redistribute #}
-{# need to check. doesn't work. 'metric' and 'route_map' match also only 'route_map' #}
+{#- redistribute afi ipv6 #}
{%- if 'redistribute' in bgp_afi[type] %}
-{%- if 'table' in bgp_afi[type].redistribute %}
- redistribute table {{bgp_afi[type].redistribute.table}}
-{%- endif %}
{%- for protocol in bgp_afi[type].redistribute %}
-{%- if ( ('metric' and 'route_map') in bgp_afi[type].redistribute[protocol] ) %}
+{%- if ( ('route_map' in bgp_afi[type].redistribute[protocol]) and ('metric' in bgp_afi[type].redistribute[protocol] ) ) %}
redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} route-map {{bgp_afi[type].redistribute[protocol].route_map}}
{%- elif 'metric' in bgp_afi[type].redistribute[protocol] %}
- redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}}
+ redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}}
{%- elif 'route_map' in bgp_afi[type].redistribute[protocol] %}
redistribute {{protocol}} route-map {{bgp_afi[type].redistribute[protocol].route_map}}
-{%- else %}
+{%- elif 'table' in bgp_afi[type].redistribute %}
+ redistribute table {{bgp_afi[type].redistribute.table}}
+{%- else %}
redistribute {{protocol}}
{%- endif %}
{%- endfor %}
@@ -206,7 +200,7 @@ router bgp {{ asn }}
neighbor {{ pr_group }} update-source {{ conf_peer_group.update_source }}
{%- endif %}
-{# START peer-group afi; set protocols bgp xxx peer-group FOO address-family #}
+{#- START peer-group afi; set protocols bgp xxx peer-group FOO address-family #}
{%- if 'address_family' in conf_peer_group %}
{%- for afi in conf_peer_group.address_family %}
{%- if afi == "ipv4_unicast" %}
@@ -236,10 +230,19 @@ router bgp {{ asn }}
{%- endif %}
{#- END single params for peer-group #}
-{#- Checks need to be done as-path|med|next-hop #}
{%- if 'attribute_unchanged' in conf_peer_group.address_family.ipv4_unicast %}
-{%- if 'as_path' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged %}
+{%- if ( ('as_path' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) and ('med' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) ) %}
+ neighbor {{ pr_group }} attribute-unchanged as-path med
+{%- elif ( ('as_path' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) and ('next_hop' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) ) %}
+ neighbor {{ pr_group }} attribute-unchanged as-path next-hop
+{%- elif ( ('med' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) and ('next_hop' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged) ) %}
+ neighbor {{ pr_group }} attribute-unchanged med next-hop
+{%- elif 'as_path' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged %}
neighbor {{ pr_group }} attribute-unchanged as-path
+{%- elif 'med' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged %}
+ neighbor {{ pr_group }} attribute-unchanged med
+{%- elif 'next_hop' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged %}
+ neighbor {{ pr_group }} attribute-unchanged next-hop
{%- else %}
neighbor {{ pr_group }} attribute-unchanged as-path next-hop med
{%- endif %}
@@ -247,11 +250,13 @@ router bgp {{ asn }}
{#- END attribute-unchanged #}
{%- if 'capability' in conf_peer_group.address_family.ipv4_unicast %}
-{%- if 'receive' in conf_peer_group.address_family.ipv4_unicast.capability.orf.prefix_list %}
+{%- if 'orf' in conf_peer_group.address_family.ipv4_unicast.capability %}
+{%- if 'receive' in conf_peer_group.address_family.ipv4_unicast.capability.orf.prefix_list %}
neighbor {{ pr_group }} capability orf prefix-list receive
-{%- endif %}
-{%- if 'send' in conf_peer_group.address_family.ipv4_unicast.capability.orf.prefix_list %}
+{%- endif %}
+{%- if 'send' in conf_peer_group.address_family.ipv4_unicast.capability.orf.prefix_list %}
neighbor {{ pr_group }} capability orf prefix-list send
+{%- endif %}
{%- endif %}
{%- endif %}
@@ -322,7 +327,6 @@ router bgp {{ asn }}
{%- endif %}
{%- endif %}
-{#- Need to check. https://phabricator.vyos.net/T2387#73900 #}
{%- if 'unsuppress_map' in conf_peer_group.address_family.ipv4_unicast %}
neighbor {{ pr_group }} unsuppress-map {{conf_peer_group.address_family.ipv4_unicast.unsuppress_map}}
{%- endif %}
@@ -357,21 +361,39 @@ router bgp {{ asn }}
{%- endif %}
{#- END single params for peer-group afi6 #}
-{#- Checks need to be done as-path|med|next-hop #}
{%- if 'attribute_unchanged' in conf_peer_group.address_family.ipv6_unicast %}
-{%- if 'as_path' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged %}
+{%- if ( ('as_path' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) and ('med' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) ) %}
+ neighbor {{ pr_group }} attribute-unchanged as-path med
+{%- elif ( ('as_path' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) and ('next_hop' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) ) %}
+ neighbor {{ pr_group }} attribute-unchanged as-path next-hop
+{%- elif ( ('med' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) and ('next_hop' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged) ) %}
+ neighbor {{ pr_group }} attribute-unchanged med next-hop
+{%- elif 'as_path' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged %}
neighbor {{ pr_group }} attribute-unchanged as-path
+{%- elif 'med' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged %}
+ neighbor {{ pr_group }} attribute-unchanged med
+{%- elif 'next_hop' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged %}
+ neighbor {{ pr_group }} attribute-unchanged next-hop
{%- else %}
neighbor {{ pr_group }} attribute-unchanged as-path next-hop med
{%- endif %}
{%- endif %}
+{#- END attribute-unchanged ipv6 #}
{%- if 'capability' in conf_peer_group.address_family.ipv6_unicast %}
-{%- if 'receive' in conf_peer_group.address_family.ipv6_unicast.capability.orf.prefix_list %}
+{%- if 'dynamic' in conf_peer_group.address_family.ipv6_unicast.capability %}
+{#- exit from afi ipv6 unicast because 'dynamic' its a global parameter for peer-group in afi6. Other checks are ongoing in afi6. Also related T3037 #}
+ exit-address-family
+ neighbor {{ pr_group }} capability dynamic
+ address-family ipv6 unicast
+{%- endif %}
+{%- if 'orf' in conf_peer_group.address_family.ipv6_unicast.capability %}
+{%- if 'receive' in conf_peer_group.address_family.ipv6_unicast.capability.orf.prefix_list %}
neighbor {{ pr_group }} capability orf prefix-list receive
{%- endif %}
-{%- if 'send' in conf_peer_group.address_family.ipv6_unicast.capability.orf.prefix_list %}
+{%- if 'send' in conf_peer_group.address_family.ipv6_unicast.capability.orf.prefix_list %}
neighbor {{ pr_group }} capability orf prefix-list send
+{%- endif %}
{%- endif %}
{%- endif %}
@@ -442,7 +464,6 @@ router bgp {{ asn }}
{%- endif %}
{%- endif %}
-{#- Checks need to be done. https://phabricator.vyos.net/T2387#73900 #}
{%- if 'unsuppress_map' in conf_peer_group.address_family.ipv6_unicast %}
neighbor {{ pr_group }} unsuppress-map {{conf_peer_group.address_family.ipv6_unicast.unsuppress_map}}
{%- endif %}
@@ -453,7 +474,7 @@ router bgp {{ asn }}
{%- endfor %}
{%- endif %}
-{# END peer-group afi; set protocols bgp xxx peer-group FOO address-family #}
+{#- END peer-group afi; set protocols bgp xxx peer-group FOO address-family #}
{%- endfor %}
{%- endif %}
@@ -464,7 +485,7 @@ router bgp {{ asn }}
{#- set peer-group as conf_peer #}
{%- set conf_peer = conf_bgp[asn].neighbor[peer] %}
-{#- First parameter for peer-group - remote-as #}
+{#- First parameter for peer neighbor - remote-as #}
{%- if 'remote_as' in conf_peer %}
neighbor {{ peer }} remote-as {{ conf_peer.remote_as }}
{%- endif %}
@@ -491,10 +512,6 @@ router bgp {{ asn }}
{%- endif %}
{%- endif %}
-{%- if 'description' in conf_peer %}
- neighbor {{ peer }} description {{ conf_peer.description }}
-{%- endif %}
-
{%- if 'disable_capability_negotiation' in conf_peer %}
neighbor {{ peer }} disable-capability-negotiation
{%- endif %}
@@ -564,17 +581,21 @@ router bgp {{ asn }}
neighbor {{ peer }} strict-capability-match
{%- endif %}
-{#- Need to check #}
+{#- set protocols bgp xxx neighbor x.x.x.x timers #}
{%- if 'timers' in conf_peer %}
-{%- if ( ('connect' and 'holdtime' and 'keepalive') in conf_peer.timers ) %}
+{%- if ( ('connect' in conf_peer.timers) and ('holdtime' in conf_peer.timers) and ('keepalive' in conf_peer.timers ) ) %}
neighbor {{ peer }} timers {{conf_peer.timers.keepalive}} {{conf_peer.timers.holdtime}}
- neighbor {{ peer }} timers connect {{conf_peer.timers.connect}}
+ neighbor {{ peer }} timers connect {{conf_peer.timers.connect}}
+{%- elif ( ('holdtime' in conf_peer.timers) and ('keepalive' in conf_peer.timers ) ) %}
+ neighbor {{ peer }} timers {{conf_peer.timers.keepalive}} {{conf_peer.timers.holdtime}}
+{%- elif 'connect' in conf_peer.timers %}
+ neighbor {{ peer }} timers connect {{conf_peer.timers.connect}}
{%- endif %}
{%- endif %}
{%- if 'ttl_security' in conf_peer %}
{%- if 'hops' in conf_peer.ttl_security %}
- neighbor {{ peer }} ttl-security hops {{conf_peer.ttl_security.hops}}
+ neighbor {{ peer }} ttl-security hops {{conf_peer.ttl_security.hops}}
{%- endif %}
{%- endif %}
@@ -582,6 +603,10 @@ router bgp {{ asn }}
neighbor {{ peer }} update-source {{ conf_peer.update_source }}
{%- endif %}
+{%- if 'description' in conf_peer %}
+ neighbor {{ peer }} description {{ conf_peer.description }}
+{%- endif %}
+
{#- START address family for peer; set protocols bgp xxx neighbor x.x.x.x address-family ipvX-unicast #}
{%- if 'address_family' in conf_peer %}
{%- for afi in conf_peer.address_family %}
@@ -615,10 +640,19 @@ router bgp {{ asn }}
{%- endif %}
{#- END single params for neighbor #}
-{#- Checks need to be done as-path|med|next-hop #}
{%- if 'attribute_unchanged' in conf_peer.address_family.ipv4_unicast %}
-{%- if 'as_path' in conf_peer.address_family.ipv4_unicast.attribute_unchanged %}
+{%- if ( ('as_path' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) and ('med' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) ) %}
+ neighbor {{ peer }} attribute-unchanged as-path med
+{%- elif ( ('as_path' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) and ('next_hop' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) ) %}
+ neighbor {{ peer }} attribute-unchanged as-path next-hop
+{%- elif ( ('med' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) and ('next_hop' in conf_peer.address_family.ipv4_unicast.attribute_unchanged) ) %}
+ neighbor {{ peer }} attribute-unchanged med next-hop
+{%- elif 'as_path' in conf_peer.address_family.ipv4_unicast.attribute_unchanged %}
neighbor {{ peer }} attribute-unchanged as-path
+{%- elif 'med' in conf_peer.address_family.ipv4_unicast.attribute_unchanged %}
+ neighbor {{ peer }} attribute-unchanged med
+{%- elif 'next_hop' in conf_peer.address_family.ipv4_unicast.attribute_unchanged %}
+ neighbor {{ peer }} attribute-unchanged next-hop
{%- else %}
neighbor {{ peer }} attribute-unchanged as-path next-hop med
{%- endif %}
@@ -626,11 +660,13 @@ router bgp {{ asn }}
{#- END attribute-unchanged #}
{%- if 'capability' in conf_peer.address_family.ipv4_unicast %}
-{%- if 'receive' in conf_peer.address_family.ipv4_unicast.capability.orf.prefix_list %}
+{%- if 'orf' in conf_peer.address_family.ipv4_unicast.capability %}
+{%- if 'receive' in conf_peer.address_family.ipv4_unicast.capability.orf.prefix_list %}
neighbor {{ peer }} capability orf prefix-list receive
-{%- endif %}
-{%- if 'send' in conf_peer.address_family.ipv4_unicast.capability.orf.prefix_list %}
+{%- endif %}
+{%- if 'send' in conf_peer.address_family.ipv4_unicast.capability.orf.prefix_list %}
neighbor {{ peer }} capability orf prefix-list send
+{%- endif %}
{%- endif %}
{%- endif %}
@@ -701,7 +737,6 @@ router bgp {{ asn }}
{%- endif %}
{%- endif %}
-{#- Checks need to be done. https://phabricator.vyos.net/T2387#73900 #}
{%- if 'unsuppress_map' in conf_peer.address_family.ipv4_unicast %}
neighbor {{ peer }} unsuppress-map {{conf_peer.address_family.ipv4_unicast.unsuppress_map}}
{%- endif %}
@@ -740,10 +775,19 @@ router bgp {{ asn }}
{%- endif %}
{#- END single params for neighbor #}
-{#- Checks need to be done as-path|med|next-hop #}
{%- if 'attribute_unchanged' in conf_peer.address_family.ipv6_unicast %}
-{%- if 'as_path' in conf_peer.address_family.ipv6_unicast.attribute_unchanged %}
+{%- if ( ('as_path' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) and ('med' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) ) %}
+ neighbor {{ peer }} attribute-unchanged as-path med
+{%- elif ( ('as_path' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) and ('next_hop' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) ) %}
+ neighbor {{ peer }} attribute-unchanged as-path next-hop
+{%- elif ( ('med' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) and ('next_hop' in conf_peer.address_family.ipv6_unicast.attribute_unchanged) ) %}
+ neighbor {{ peer }} attribute-unchanged med next-hop
+{%- elif 'as_path' in conf_peer.address_family.ipv6_unicast.attribute_unchanged %}
neighbor {{ peer }} attribute-unchanged as-path
+{%- elif 'med' in conf_peer.address_family.ipv6_unicast.attribute_unchanged %}
+ neighbor {{ peer }} attribute-unchanged med
+{%- elif 'next_hop' in conf_peer.address_family.ipv6_unicast.attribute_unchanged %}
+ neighbor {{ peer }} attribute-unchanged next-hop
{%- else %}
neighbor {{ peer }} attribute-unchanged as-path next-hop med
{%- endif %}
@@ -751,11 +795,13 @@ router bgp {{ asn }}
{#- END attribute-unchanged #}
{%- if 'capability' in conf_peer.address_family.ipv6_unicast %}
-{%- if 'receive' in conf_peer.address_family.ipv6_unicast.capability.orf.prefix_list %}
+{%- if 'orf' in conf_peer.address_family.ipv6_unicast.capability %}
+{%- if 'receive' in conf_peer.address_family.ipv6_unicast.capability.orf.prefix_list %}
neighbor {{ peer }} capability orf prefix-list receive
-{%- endif %}
-{%- if 'send' in conf_peer.address_family.ipv6_unicast.capability.orf.prefix_list %}
- neighbor {{ peer }} capability orf prefix-list send
+{%- endif %}
+{%- if 'send' in conf_peer.address_family.ipv6_unicast.capability.orf.prefix_list %}
+ neighbor {{ peer }} capability orf prefix-list send
+{%- endif %}
{%- endif %}
{%- endif %}
@@ -826,7 +872,6 @@ router bgp {{ asn }}
{%- endif %}
{%- endif %}
-{#- Checks need to be done. https://phabricator.vyos.net/T2387#73900 #}
{%- if 'unsuppress_map' in conf_peer.address_family.ipv6_unicast %}
neighbor {{ peer }} unsuppress-map {{conf_peer.address_family.ipv6_unicast.unsuppress_map}}
{%- endif %}
@@ -863,7 +908,7 @@ router bgp {{ asn }}
{%- endif %}
{%- endif %}
{%- if 'med' in bgp_params.bestpath %}
-{%- if ( ('confed' and 'missing_as_worst') in bgp_params.bestpath.med ) %}
+{%- if ( ('confed' in bgp_params.bestpath.med) and ('missing_as_worst' in bgp_params.bestpath.med ) ) %}
bgp bestpath med confed missing-as-worst
{%- elif 'confed' in bgp_params.bestpath.med %}
bgp bestpath med confed
@@ -886,9 +931,9 @@ router bgp {{ asn }}
{%- endif %}
{%- endif %}
-{#- Doesn't work in current FRR configuration (bgp dampening 16 751 2001 61) #}
+{#- Doesn't work in current FRR configuration; vtysh (bgp dampening 16 751 2001 61) #}
{%- if 'dampening' in bgp_params %}
-{%- if ( ('half_life' and 'max_suppress_time' and 're_use' and 'start_suppress_time') in bgp_params.dampening ) %}
+{%- if ( ('half_life' in bgp_params.dampening) and ('max_suppress_time' in bgp_params.dampening) and ('re_use' in bgp_params.dampening) and ('start_suppress_time' in bgp_params.dampening ) ) %}
bgp dampening {{ bgp_params.dampening.half_life }} {{ bgp_params.dampening.re_use }} {{ bgp_params.dampening.start_suppress_time }} {{ bgp_params.dampening.max_suppress_time }}
{%- endif %}
{%- endif %}
@@ -909,7 +954,7 @@ router bgp {{ asn }}
{%- if 'distance' in bgp_params %}
{%- if 'global' in bgp_params.distance %}
-{%- if ( ('external' and 'internal' and 'local') in bgp_params.distance.global ) %}
+{%- if ( ('external' in bgp_params.distance.global) and ('internal' in bgp_params.distance.global) and ('local' in bgp_params.distance.global ) ) %}
!
address-family ipv4 unicast
distance bgp {{ bgp_params.distance.global.external }} {{ bgp_params.distance.global.internal }} {{ bgp_params.distance.global.local }}
@@ -950,10 +995,14 @@ router bgp {{ asn }}
no bgp fast-external-failover
{%- endif %}
+{%- if 'router_id' in bgp_params %}
+ bgp router-id {{ bgp_params.router_id }}
+{%- endif %}
+
{#- END parameters; set protocols bgp xxx parameters #}
{%- if 'timers' in conf_bgp[asn] %}
-{%- if ( ('holdtime' and 'keepalive') in conf_bgp[asn].timers ) %}
+{%- if ( ('holdtime' in conf_bgp[asn].timers) and ('keepalive' in conf_bgp[asn].timers ) ) %}
timers bgp {{conf_bgp[asn].timers.keepalive}} {{conf_bgp[asn].timers.holdtime}}
{%- endif %}
{%- endif %}
diff --git a/data/templates/frr/ldpd.frr.tmpl b/data/templates/frr/ldpd.frr.tmpl
index 5f080d75f..81a992165 100644
--- a/data/templates/frr/ldpd.frr.tmpl
+++ b/data/templates/frr/ldpd.frr.tmpl
@@ -13,6 +13,7 @@ no neighbor {{neighbor_id}} password {{old_ldp.neighbors[neighbor_id].password}}
{% for neighbor_id in ldp.neighbors -%}
neighbor {{neighbor_id}} password {{ldp.neighbors[neighbor_id].password}}
{% endfor -%}
+!
address-family ipv4
label local allocate host-routes
{% if old_ldp.export_ipv4_exp -%}
@@ -27,20 +28,20 @@ no discovery transport-address {{ old_ldp.d_transp_ipv4 }}
{% if ldp.d_transp_ipv4 -%}
discovery transport-address {{ ldp.d_transp_ipv4 }}
{% endif -%}
-{% if old_ldp.hello_holdtime -%}
-no discovery hello holdtime {{ old_ldp.hello_holdtime }}
+{% if old_ldp.hello_ipv4_holdtime -%}
+no discovery hello holdtime {{ old_ldp.hello_ipv4_holdtime }}
{% endif -%}
-{% if ldp.hello_holdtime -%}
-discovery hello holdtime {{ ldp.hello_holdtime }}
+{% if ldp.hello_ipv4_holdtime -%}
+discovery hello holdtime {{ ldp.hello_ipv4_holdtime }}
{% endif -%}
-{% if old_ldp.hello_interval -%}
-no discovery hello interval {{ old_ldp.hello_interval }}
+{% if old_ldp.hello_ipv4_interval -%}
+no discovery hello interval {{ old_ldp.hello_ipv4_interval }}
{% endif -%}
-{% if ldp.hello_interval -%}
-discovery hello interval {{ ldp.hello_interval }}
+{% if ldp.hello_ipv4_interval -%}
+discovery hello interval {{ ldp.hello_ipv4_interval }}
{% endif -%}
{% if old_ldp.ses_ipv4_hold -%}
-no session holdtime {{ old_ldp.ses_ipv4_hold }}
+no session holdtime {{ old_ldp.ses_ipv4_hold }}
{% endif -%}
{% if ldp.ses_ipv4_hold -%}
session holdtime {{ ldp.ses_ipv4_hold }}
@@ -65,7 +66,7 @@ no label local advertise explicit-null
label local advertise explicit-null
{% endif -%}
{% if old_ldp.ses_ipv6_hold -%}
-no session holdtime {{ 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 }}
@@ -76,6 +77,18 @@ no discovery transport-address {{ old_ldp.d_transp_ipv6 }}
{% if ldp.d_transp_ipv6 -%}
discovery transport-address {{ ldp.d_transp_ipv6 }}
{% endif -%}
+{% if old_ldp.hello_ipv6_holdtime -%}
+no discovery hello holdtime {{ old_ldp.hello_ipv6_holdtime }}
+{% endif -%}
+{% if ldp.hello_ipv6_holdtime -%}
+discovery hello holdtime {{ ldp.hello_ipv6_holdtime }}
+{% endif -%}
+{% if old_ldp.hello_ipv6_interval -%}
+no discovery hello interval {{ old_ldp.hello_ipv6_interval }}
+{% endif -%}
+{% if ldp.hello_ipv6_interval -%}
+discovery hello interval {{ ldp.hello_ipv6_interval }}
+{% endif -%}
{% for interface in old_ldp.interfaces -%}
no interface {{interface}}
{% endfor -%}
diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl
index a20be45ae..855ebff4f 100644
--- a/data/templates/https/nginx.default.tmpl
+++ b/data/templates/https/nginx.default.tmpl
@@ -5,7 +5,7 @@ server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
- return 301 https://$server_name$request_uri;
+ return 301 https://$host$request_uri;
}
{% for server in server_block_list %}
diff --git a/data/templates/openvpn/client.conf.tmpl b/data/templates/openvpn/client.conf.tmpl
index 508d8da94..b57c45ce5 100644
--- a/data/templates/openvpn/client.conf.tmpl
+++ b/data/templates/openvpn/client.conf.tmpl
@@ -1,35 +1,33 @@
### Autogenerated by interfaces-openvpn.py ###
-{% if ip -%}
-ifconfig-push {{ ip[0] }} {{ remote_netmask }}
-{% endif -%}
-
-{% for route in push_route -%}
-push "route {{ route }}"
-{% endfor -%}
-
-{% for net in subnet -%}
-iroute {{ net }}
-{% endfor -%}
+{% if ip %}
+ifconfig-push {{ ip[0] }} {{ subnet[0] | netmask_from_cidr }}
+{% endif %}
+{% if push_route is defined and push_route is not none %}
+{% for route in push_route %}
+push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }}"
+{% endfor %}
+{% endif %}
+{% if subnet is defined and subnet is not none %}
+{% for network in subnet %}
+iroute {{ network | address_from_cidr }} {{ network | netmask_from_cidr }}
+{% endfor %}
+{% endif %}
{# ipv6_remote is only set when IPv6 server is enabled #}
-{% if ipv6_remote -%}
+{% if ipv6_remote %}
# IPv6
-
-{%- if ipv6_ip %}
+{% if ipv6_ip %}
ifconfig-ipv6-push {{ ipv6_ip[0] }} {{ ipv6_remote }}
-{%- endif %}
-
-{%- for route6 in ipv6_push_route %}
+{% endif %}
+{% for route6 in ipv6_push_route %}
push "route-ipv6 {{ route6 }}"
-{%- endfor %}
-
-{%- for net6 in ipv6_subnet %}
+{% endfor %}
+{% for net6 in ipv6_subnet %}
iroute {{ net6 }}
-{%- endfor %}
-
-{% endif -%}
+{% endfor %}
+{% endif %}
-{% if disable -%}
+{% if disable is defined %}
disable
-{% endif -%}
+{% endif %}
diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl
index fea310236..66da9c794 100644
--- a/data/templates/openvpn/server.conf.tmpl
+++ b/data/templates/openvpn/server.conf.tmpl
@@ -2,246 +2,232 @@
#
# See https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
# for individual keyword definition
-
-{% if description -%}
-# {{ description }}
-
-{% endif -%}
+#
+# {{ description if description is defined and description is not none }}
+#
verb 3
-
-user {{ uid }}
-group {{ gid }}
-
-dev-type {{ type }}
-dev {{ intf }}
+user {{ daemon_user }}
+group {{ daemon_group }}
+dev-type {{ device_type }}
+dev {{ ifname }}
persist-key
iproute /usr/libexec/vyos/system/unpriv-ip
-
-proto {{ protocol_real }}
-
-{%- if local_host %}
+proto {{ protocol }}
+{% if local_host is defined and local_host is not none %}
local {{ local_host }}
-{%- endif %}
-
-{%- if mode == 'server' and protocol == 'udp' and not local_host %}
+{% endif %}
+{% if mode is defined and mode == 'server' and protocol == 'udp' and local_host is not defined %}
multihome
-{%- endif %}
-
-{%- if local_port %}
+{% endif %}
+{% if local_port is defined and local_port is not none %}
lport {{ local_port }}
-{%- endif %}
-
-{% if remote_port -%}
+{% endif %}
+{% if remote_port is defined and remote_port is not none %}
rport {{ remote_port }}
{% endif %}
-
-{%- if remote_host %}
-{%- for remote in remote_host -%}
+{% if remote_host is defined and remote_host is not none %}
+{% for remote in remote_host %}
remote {{ remote }}
-{% endfor -%}
-{% endif -%}
-
-{% if shared_secret_file %}
-secret {{ shared_secret_file }}
-{%- endif %}
-
-{%- if persistent_tunnel %}
+{% endfor %}
+{% endif %}
+{% if shared_secret_key_file is defined and shared_secret_key_file is not none %}
+secret {{ shared_secret_key_file }}
+{% endif %}
+{% if persistent_tunnel is defined %}
persist-tun
-{%- endif %}
-
-{%- if redirect_gateway %}
-push "redirect-gateway {{ redirect_gateway }}"
-{%- endif %}
-
-{%- if compress_lzo %}
+{% endif %}
+{% if replace_default_route is defined and replace_default_route.local is defined %}
+push "redirect-gateway local def1"
+{% elif replace_default_route is defined %}
+push "redirect-gateway def1"
+{% endif %}
+{% if use_lzo_compression is defined %}
compress lzo
-{%- endif %}
+{% endif %}
-{% if 'client' in mode -%}
+{% if 'client' in mode %}
#
# OpenVPN Client mode
#
client
nobind
-
-{% elif 'server' in mode -%}
+{% elif 'server' in mode %}
#
# OpenVPN Server mode
#
-
-{%- if server_topology %}
-topology {% if server_topology == 'point-to-point' %}p2p{% else %}{{ server_topology }}{% endif %}
-{%- endif %}
-
-{%- if is_bridge_member %}
mode server
tls-server
-{%- else %}
-server {{ server_subnet[0] }} nopool
-{%- endif %}
-
-{%- if server_pool %}
-ifconfig-pool {{ server_pool_start }} {{ server_pool_stop }}{% if server_pool_netmask %} {{ server_pool_netmask }}{% endif %}
-{%- endif %}
-
-{%- if server_max_conn %}
-max-clients {{ server_max_conn }}
-{%- endif %}
-
-{%- if client %}
-client-config-dir /run/openvpn/ccd/{{ intf }}
-{%- endif %}
-
-{%- if server_reject_unconfigured %}
-ccd-exclusive
-{%- endif %}
-
-keepalive {{ ping_interval }} {{ ping_restart }}
+{% if server is defined and server is not none %}
+{% if server.subnet is defined and server.subnet is not none %}
+{% for subnet in server.subnet if subnet | ipv4 %}
+server {{ subnet | address_from_cidr }} {{ subnet | netmask_from_cidr }} nopool
+{# OpenVPN assigns the first IP address to its local interface so the pool used #}
+{# in net30 topology - where each client receives a /30 must start from the second subnet #}
+{% if server.topology is defined and server.topology == 'net30' %}
+ifconfig-pool {{ subnet | inc_ip('4') }} {{ subnet | last_host_address }} {{ subnet | netmask_from_cidr if device_type == 'tap' else '' }}
+{% else %}
+{# OpenVPN assigns the first IP address to its local interface so the pool must #}
+{# start from the second address and end on the last address #}
+ifconfig-pool {{ subnet | first_host_address | inc_ip('1') }} {{ subnet | last_host_address }} {{ subnet | netmask_from_cidr if device_type == 'tap' else '' }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% if server.topology is defined and server.topology == 'point-to-point' %}
+topology p2p
+{% elif server.topology is defined and server.topology is not none %}
+topology {{ server.topology }}
+{% endif %}
+{% if server.client_ip_pool is defined and server.client_ip_pool is not none and server.client_ip_pool.disable is not defined %}
+ifconfig-pool {{ server.client_ip_pool.start }} {{ server.client_ip_pool.stop }}{{ server.client_ip_pool.subnet_mask if server.client_ip_pool.subnet_mask is defined and server.client_ip_pool.subnet_mask is not none }}
+{% endif %}
+{% if server.max_connections is defined and server.max_connections is not none %}
+max-clients {{ server.max_connections }}
+{% endif %}
+{% if server.client is defined and server.client is not none %}
+client-config-dir /run/openvpn/ccd/{{ ifname }}
+{% endif %}
+{% endif %}
+keepalive {{ keep_alive.interval }} {{ keep_alive.failure_count }}
management /run/openvpn/openvpn-mgmt-intf unix
-
-{% for route in server_push_route -%}
+{% if server is defined and server is not none %}
+{% if server.reject_unconfigured_clients is defined %}
+ccd-exclusive
+{% endif %}
+{% if server.push_route is defined and server.push_route is not none %}
+{% for route in server.push_route %}
push "route {{ route }}"
-{% endfor -%}
-
-{% for ns in server_dns_nameserver -%}
-push "dhcp-option DNS {{ ns }}"
-{% endfor -%}
-
-{%- if server_domain -%}
-push "dhcp-option DOMAIN {{ server_domain }}"
-{% endif -%}
-
-{%- if server_ipv6_local %}
+{% endfor %}
+{% endif %}
+{% if server.name_server is defined and server.name_server is not none %}
+{% for nameserver in server.name_server %}
+push "dhcp-option DNS {{ nameserver }}"
+{% endfor %}
+{% endif %}
+{% if server.domain_name is defined and server.domain_name is not none %}
+push "dhcp-option DOMAIN {{ server.domain_name }}"
+{% endif %}
+{% endif %}
+
+{% if subnet_v6 is defined and subnet_v6 is not none %}
# IPv6
push "tun-ipv6"
ifconfig-ipv6 {{ server_ipv6_local }}/{{ server_ipv6_prefixlen }} {{ server_ipv6_remote }}
-
-{%- if server_ipv6_pool %}
+{% if server_ipv6_pool %}
ifconfig-ipv6-pool {{ server_ipv6_pool_base }}/{{ server_ipv6_pool_prefixlen }}
-{%- endif %}
-
-{%- for route6 in server_ipv6_push_route %}
+{% endif %}
+{% for route6 in server_ipv6_push_route %}
push "route-ipv6 {{ route6 }}"
-{%- endfor %}
-
-{%- for ns6 in server_ipv6_dns_nameserver %}
+{% endfor %}
+{% for ns6 in server_ipv6_dns_nameserver %}
push "dhcp-option DNS6 {{ ns6 }}"
-{%- endfor %}
-
-{%- endif %}
-
-{% else -%}
+{% endfor %}
+{% endif %}
+{% else %}
#
# OpenVPN site-2-site mode
#
-ping {{ ping_interval }}
-ping-restart {{ ping_restart }}
-
-{% if local_address_subnet -%}
-ifconfig {{ local_address[0] }} {{ local_address_subnet }}
-{%- elif remote_address -%}
-ifconfig {{ local_address[0] }} {{ remote_address[0] }}
-{%- endif %}
-
-{% if ipv6_local_address -%}
-ifconfig-ipv6 {{ ipv6_local_address[0] }} {{ ipv6_remote_address[0] }}
-{%- endif %}
-
-{% endif -%}
+ping {{ keep_alive.interval }}
+ping-restart {{ keep_alive.failure_count }}
+
+{% for laddr, laddr_conf in local_address.items() if laddr | ipv4 %}
+{% if laddr_conf is defined and laddr_conf.subnet_mask is defined and laddr_conf.subnet_mask is not none %}
+ifconfig {{ laddr }} {{ laddr_conf.subnet_mask }}
+{% else %}
+{% for raddr in remote_address %}
+{% if raddr | ipv4 %}
+ifconfig {{ laddr }} {{ raddr }}
+{% else %}
+ifconfig-ipv6 {{ laddr }} {{ raddr }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
-{% if tls -%}
+{% if tls is defined and tls is not none %}
# TLS options
-{%- if tls_ca_cert %}
-ca {{ tls_ca_cert }}
-{%- endif %}
-
-{%- if tls_cert %}
-cert {{ tls_cert }}
-{%- endif %}
-
-{%- if tls_key %}
-key {{ tls_key }}
-{%- endif %}
-
-{%- if tls_crypt %}
-tls-crypt {{ tls_crypt }}
-{%- endif %}
-
-{%- if tls_crl %}
-crl-verify {{ tls_crl }}
-{%- endif %}
-
-{%- if tls_version_min %}
-tls-version-min {{tls_version_min}}
-{%- endif %}
-
-{%- if tls_dh %}
-dh {{ tls_dh }}
-{%- endif %}
-
-{%- if tls_auth %}
-{%- if mode == 'client' %}
-tls-auth {{tls_auth}} 1
-{%- elif mode == 'server' %}
-tls-auth {{tls_auth}} 0
-{%- endif %}
-{%- endif %}
-
-{%- if tls_role %}
-{%- if 'active' in tls_role %}
+{% if tls.ca_cert_file is defined and tls.ca_cert_file is not none %}
+ca {{ tls.ca_cert_file }}
+{% endif %}
+{% if tls.cert_file is defined and tls.cert_file is not none %}
+cert {{ tls.cert_file }}
+{% endif %}
+{% if tls.key_file is defined and tls.key_file is not none %}
+key {{ tls.key_file }}
+{% endif %}
+{% if tls.crypt_file is defined and tls.crypt_file is not none %}
+tls-crypt {{ tls.crypt_file }}
+{% endif %}
+{% if tls.crl_file is defined and tls.crl_file is not none %}
+crl-verify {{ tls.crl_file }}
+{% endif %}
+{% if tls.tls_version_min is defined and tls.tls_version_min is not none %}
+tls-version-min {{ tls.tls_version_min }}
+{% endif %}
+{% if tls.dh_file is defined and tls.dh_file is not none %}
+dh {{ tls.dh_file }}
+{% endif %}
+{% if tls.auth_file is defined and tls.auth_file is not none %}
+{% if mode == 'client' %}
+tls-auth {{ tls.auth_file }} 1
+{% elif mode == 'server' %}
+tls-auth {{ tls.auth_file }} 0
+{% endif %}
+{% endif %}
+{% if tls.role is defined and tls.role is not none %}
+{% if tls.role == 'active' %}
tls-client
-{%- elif 'passive' in tls_role %}
+{% elif tls.role == 'passive' %}
tls-server
-{%- endif %}
-{%- endif %}
-
-{%- endif %}
+{% endif %}
+{% endif %}
+{% endif %}
# Encryption options
-{%- if encryption %}
-{% if encryption == 'none' -%}
+{% if encryption is defined and encryption is not none %}
+{% if encryption.cipher is defined and encryption.cipher is not none %}
+{% if encryption.cipher == 'none' %}
cipher none
-{%- elif encryption == 'des' -%}
+{% elif encryption.cipher == 'des' %}
cipher des-cbc
-{%- elif encryption == '3des' -%}
+{% elif encryption.cipher == '3des' %}
cipher des-ede3-cbc
-{%- elif encryption == 'bf128' -%}
+{% elif encryption.cipher == 'bf128' %}
cipher bf-cbc
keysize 128
-{%- elif encryption == 'bf256' -%}
+{% elif encryption.cipher == 'bf256' %}
cipher bf-cbc
keysize 25
-{%- elif encryption == 'aes128gcm' -%}
+{% elif encryption.cipher == 'aes128gcm' %}
cipher aes-128-gcm
-{%- elif encryption == 'aes128' -%}
+{% elif encryption.cipher == 'aes128' %}
cipher aes-128-cbc
-{%- elif encryption == 'aes192gcm' -%}
+{% elif encryption.cipher == 'aes192gcm' %}
cipher aes-192-gcm
-{%- elif encryption == 'aes192' -%}
+{% elif encryption.cipher == 'aes192' %}
cipher aes-192-cbc
-{%- elif encryption == 'aes256gcm' -%}
+{% elif encryption.cipher == 'aes256gcm' %}
cipher aes-256-gcm
-{%- elif encryption == 'aes256' -%}
+{% elif encryption.cipher == 'aes256' %}
cipher aes-256-cbc
-{%- endif -%}
-{%- endif %}
-
-{%- if ncp_ciphers %}
-ncp-ciphers {{ncp_ciphers}}
-{%- endif %}
-{%- if disable_ncp %}
+{% endif %}
+{% endif %}
+{% if encryption.ncp_ciphers is defined and encryption.ncp_ciphers is not none %}
+ncp-ciphers {{ encryption.ncp_ciphers | join(':') }}
+{% elif encryption.disable_ncp is defined %}
ncp-disable
-{%- endif %}
+{% endif %}
+{% endif %}
-{% if hash -%}
+{% if hash is defined and hash is not none %}
auth {{ hash }}
-{%- endif -%}
+{% endif %}
-{%- if auth %}
+{% if authentication is defined and authentication is not none %}
auth-user-pass {{ auth_user_pass_file }}
auth-retry nointeract
-{%- endif %}
+{% endif %}
# DEPRECATED This option will be removed in OpenVPN 2.5
# Until OpenVPN v2.3 the format of the X.509 Subject fields was formatted like this:
@@ -257,12 +243,11 @@ auth-retry nointeract
# See https://phabricator.vyos.net/T1512
compat-names
-{% if options -%}
+{% if openvpn_option is defined and openvpn_option is not none %}
#
# Custom options added by user (not validated)
#
-
-{% for option in options -%}
+{% for option in openvpn_option %}
{{ option }}
-{% endfor -%}
-{%- endif %}
+{% endfor %}
+{% endif %}
diff --git a/debian/control b/debian/control
index ebcfc6c43..cd05fa966 100644
--- a/debian/control
+++ b/debian/control
@@ -10,6 +10,7 @@ Build-Depends:
python3,
python3-coverage,
python3-lxml,
+ python3-netifaces,
python3-nose,
python3-setuptools,
python3-xmltodict,
@@ -65,6 +66,7 @@ Depends:
ntpdate,
ocserv,
openssh-server,
+ openssl,
openvpn,
openvpn-auth-ldap,
openvpn-auth-radius,
diff --git a/debian/rules b/debian/rules
index 6b982fd8e..a0cc7a99b 100755
--- a/debian/rules
+++ b/debian/rules
@@ -97,6 +97,10 @@ override_dh_auto_install:
mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/smoke/
cp -r smoketest/scripts/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/smoke
+ # Install smoke test configs
+ mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config/
+ cp -r smoketest/configs/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/config
+
# Install system programs
mkdir -p $(DIR)/$(VYOS_BIN_DIR)
cp -r smoketest/bin/* $(DIR)/$(VYOS_BIN_DIR)
diff --git a/debian/vyos-1x-smoketest.install b/debian/vyos-1x-smoketest.install
index fdf949557..3739763b9 100644
--- a/debian/vyos-1x-smoketest.install
+++ b/debian/vyos-1x-smoketest.install
@@ -1,2 +1,4 @@
usr/bin/vyos-smoketest
+usr/bin/vyos-configtest
usr/libexec/vyos/tests/smoke
+usr/libexec/vyos/tests/config
diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in
index ca8abc036..978118b31 100644
--- a/interface-definitions/dhcp-server.xml.in
+++ b/interface-definitions/dhcp-server.xml.in
@@ -87,6 +87,7 @@
<constraint>
<validator name="ipv4-prefix"/>
</constraint>
+ <constraintErrorMessage>Invalid IPv4 subnet definition</constraintErrorMessage>
</properties>
<children>
<leafNode name="bootfile-name">
diff --git a/interface-definitions/include/accel-radius-additions-disable-accounting.xlm.in b/interface-definitions/include/accel-radius-additions-disable-accounting.xlm.in
new file mode 100644
index 000000000..026f67453
--- /dev/null
+++ b/interface-definitions/include/accel-radius-additions-disable-accounting.xlm.in
@@ -0,0 +1,7 @@
+<leafNode name="disable-accounting">
+ <properties>
+ <help>Disable accounting</help>
+ <valueless/>
+ </properties>
+</leafNode>
+
diff --git a/interface-definitions/include/accel-radius-additions.xml.i b/interface-definitions/include/accel-radius-additions.xml.i
index 598fb73f8..bf0f0ac94 100644
--- a/interface-definitions/include/accel-radius-additions.xml.i
+++ b/interface-definitions/include/accel-radius-additions.xml.i
@@ -29,6 +29,7 @@
</properties>
<defaultValue>1813</defaultValue>
</leafNode>
+ #include <include/accel-radius-additions-disable-accounting.xlm.in>
<leafNode name="fail-time">
<properties>
<help>Mark server unavailable for &lt;n&gt; seconds on failure</help>
diff --git a/interface-definitions/include/bgp-neighbor-afi-ipv4-unicast.xml.i b/interface-definitions/include/bgp-neighbor-afi-ipv4-unicast.xml.i
index f1a61d669..155817838 100644
--- a/interface-definitions/include/bgp-neighbor-afi-ipv4-unicast.xml.i
+++ b/interface-definitions/include/bgp-neighbor-afi-ipv4-unicast.xml.i
@@ -267,7 +267,6 @@
<leafNode name="unsuppress-map">
<properties>
<help>Route-map to selectively unsuppress suppressed IPv4-routes</help>
- <valueless/>
</properties>
</leafNode>
<leafNode name="weight">
diff --git a/interface-definitions/include/bgp-neighbor-afi-ipv6-unicast.xml.i b/interface-definitions/include/bgp-neighbor-afi-ipv6-unicast.xml.i
index dcdc0eb66..63bf582db 100644
--- a/interface-definitions/include/bgp-neighbor-afi-ipv6-unicast.xml.i
+++ b/interface-definitions/include/bgp-neighbor-afi-ipv6-unicast.xml.i
@@ -59,6 +59,13 @@
<help>Advertise capabilities to this neighbor (IPv6)</help>
</properties>
<children>
+ <!-- Capability dynamic in the afi ipv6 does nothing T3037 -->
+ <leafNode name="dynamic">
+ <properties>
+ <help>Advertise dynamic capability to this neighbor</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<node name="orf">
<properties>
<help>Advertise ORF capability to this neighbor</help>
diff --git a/interface-definitions/include/interface-mtu-1200-16000.xml.i b/interface-definitions/include/interface-mtu-1200-16000.xml.i
new file mode 100644
index 000000000..04b5ec8ac
--- /dev/null
+++ b/interface-definitions/include/interface-mtu-1200-16000.xml.i
@@ -0,0 +1,16 @@
+<!-- included start from interface-mtu-1200-16000.xml.i -->
+<leafNode name="mtu">
+ <properties>
+ <help>Maximum Transmission Unit (MTU)</help>
+ <valueHelp>
+ <format>1200-16000</format>
+ <description>Maximum Transmission Unit in byte</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1200-16000"/>
+ </constraint>
+ <constraintErrorMessage>MTU must be between 1200 and 16000</constraintErrorMessage>
+ </properties>
+ <defaultValue>1500</defaultValue>
+</leafNode>
+<!-- included end -->
diff --git a/interface-definitions/include/interface-mtu-1200-9000.xml.i b/interface-definitions/include/interface-mtu-1200-9000.xml.i
deleted file mode 100644
index 387e60fa5..000000000
--- a/interface-definitions/include/interface-mtu-1200-9000.xml.i
+++ /dev/null
@@ -1,16 +0,0 @@
-<!-- included start from interface-mtu-1200-9000.xml.i -->
-<leafNode name="mtu">
- <properties>
- <help>Maximum Transmission Unit (MTU)</help>
- <valueHelp>
- <format>1200-9000</format>
- <description>Maximum Transmission Unit</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1200-9000"/>
- </constraint>
- <constraintErrorMessage>MTU must be between 1200 and 9000</constraintErrorMessage>
- </properties>
- <defaultValue>1500</defaultValue>
-</leafNode>
-<!-- included end -->
diff --git a/interface-definitions/include/interface-mtu-1450-16000.xml.i b/interface-definitions/include/interface-mtu-1450-16000.xml.i
new file mode 100644
index 000000000..41dd5fb00
--- /dev/null
+++ b/interface-definitions/include/interface-mtu-1450-16000.xml.i
@@ -0,0 +1,16 @@
+<!-- included start from interface-mtu-1450-16000.xml.i -->
+<leafNode name="mtu">
+ <properties>
+ <help>Maximum Transmission Unit (MTU)</help>
+ <valueHelp>
+ <format>1450-16000</format>
+ <description>Maximum Transmission Unit in byte</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1450-16000"/>
+ </constraint>
+ <constraintErrorMessage>MTU must be between 1450 and 16000</constraintErrorMessage>
+ </properties>
+ <defaultValue>1500</defaultValue>
+</leafNode>
+<!-- included end -->
diff --git a/interface-definitions/include/interface-mtu-1450-9000.xml.i b/interface-definitions/include/interface-mtu-1450-9000.xml.i
deleted file mode 100644
index 3fc961051..000000000
--- a/interface-definitions/include/interface-mtu-1450-9000.xml.i
+++ /dev/null
@@ -1,16 +0,0 @@
-<!-- included start from interface-mtu-1450-9000.xml.i -->
-<leafNode name="mtu">
- <properties>
- <help>Maximum Transmission Unit (MTU)</help>
- <valueHelp>
- <format>1450-9000</format>
- <description>Maximum Transmission Unit</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1450-9000"/>
- </constraint>
- <constraintErrorMessage>MTU must be between 1450 and 9000</constraintErrorMessage>
- </properties>
- <defaultValue>1500</defaultValue>
-</leafNode>
-<!-- included end -->
diff --git a/interface-definitions/include/interface-mtu-64-8024.xml.i b/interface-definitions/include/interface-mtu-64-8024.xml.i
index f51e098c1..0a455bc64 100644
--- a/interface-definitions/include/interface-mtu-64-8024.xml.i
+++ b/interface-definitions/include/interface-mtu-64-8024.xml.i
@@ -4,7 +4,7 @@
<help>Maximum Transmission Unit (MTU)</help>
<valueHelp>
<format>64-8024</format>
- <description>Maximum Transmission Unit</description>
+ <description>Maximum Transmission Unit in byte</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 64-8024"/>
diff --git a/interface-definitions/include/interface-mtu-68-1500.xml.i b/interface-definitions/include/interface-mtu-68-1500.xml.i
index 0563e0023..78c2c6920 100644
--- a/interface-definitions/include/interface-mtu-68-1500.xml.i
+++ b/interface-definitions/include/interface-mtu-68-1500.xml.i
@@ -4,7 +4,7 @@
<help>Maximum Transmission Unit (MTU)</help>
<valueHelp>
<format>68-1500</format>
- <description>Maximum Transmission Unit</description>
+ <description>Maximum Transmission Unit in byte</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 68-1500"/>
diff --git a/interface-definitions/include/interface-mtu-68-16000.xml.i b/interface-definitions/include/interface-mtu-68-16000.xml.i
new file mode 100644
index 000000000..9f18464bf
--- /dev/null
+++ b/interface-definitions/include/interface-mtu-68-16000.xml.i
@@ -0,0 +1,16 @@
+<!-- included start from interface-mtu-68-16000.xml.i -->
+<leafNode name="mtu">
+ <properties>
+ <help>Maximum Transmission Unit (MTU)</help>
+ <valueHelp>
+ <format>68-16000</format>
+ <description>Maximum Transmission Unit in byte</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 68-16000"/>
+ </constraint>
+ <constraintErrorMessage>MTU must be between 68 and 16000</constraintErrorMessage>
+ </properties>
+ <defaultValue>1500</defaultValue>
+</leafNode>
+<!-- included end -->
diff --git a/interface-definitions/include/interface-mtu-68-9000.xml.i b/interface-definitions/include/interface-mtu-68-9000.xml.i
deleted file mode 100644
index 82d0ed82b..000000000
--- a/interface-definitions/include/interface-mtu-68-9000.xml.i
+++ /dev/null
@@ -1,16 +0,0 @@
-<!-- included start from interface-mtu-68-9000.xml.i -->
-<leafNode name="mtu">
- <properties>
- <help>Maximum Transmission Unit (MTU)</help>
- <valueHelp>
- <format>68-9000</format>
- <description>Maximum Transmission Unit</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 68-9000"/>
- </constraint>
- <constraintErrorMessage>MTU must be between 68 and 9000</constraintErrorMessage>
- </properties>
- <defaultValue>1500</defaultValue>
-</leafNode>
-<!-- included end -->
diff --git a/interface-definitions/include/vif-s.xml.i b/interface-definitions/include/vif-s.xml.i
index 3a04b10d9..ab556489f 100644
--- a/interface-definitions/include/vif-s.xml.i
+++ b/interface-definitions/include/vif-s.xml.i
@@ -44,7 +44,7 @@
</children>
</node>
#include <include/interface-mac.xml.i>
- #include <include/interface-mtu-68-9000.xml.i>
+ #include <include/interface-mtu-68-16000.xml.i>
<tagNode name="vif-c">
<properties>
<help>QinQ TAG-C Virtual Local Area Network (VLAN) ID</help>
@@ -61,7 +61,7 @@
#include <include/interface-disable-link-detect.xml.i>
#include <include/interface-disable.xml.i>
#include <include/interface-mac.xml.i>
- #include <include/interface-mtu-68-9000.xml.i>
+ #include <include/interface-mtu-68-16000.xml.i>
#include <include/interface-vrf.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/include/vif.xml.i b/interface-definitions/include/vif.xml.i
index a0f7c0bc8..3369d0d7c 100644
--- a/interface-definitions/include/vif.xml.i
+++ b/interface-definitions/include/vif.xml.i
@@ -63,7 +63,7 @@
</children>
</node>
#include <include/interface-mac.xml.i>
- #include <include/interface-mtu-68-9000.xml.i>
+ #include <include/interface-mtu-68-16000.xml.i>
</children>
</tagNode>
<!-- included end -->
diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in
index 4e2c61d07..a72ffa01e 100644
--- a/interface-definitions/interfaces-bonding.xml.in
+++ b/interface-definitions/interfaces-bonding.xml.in
@@ -170,7 +170,7 @@
</leafNode>
</children>
</node>
- #include <include/interface-mtu-68-9000.xml.i>
+ #include <include/interface-mtu-68-16000.xml.i>
<leafNode name="primary">
<properties>
<help>Primary device interface</help>
diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in
index 1050d4571..431322b75 100644
--- a/interface-definitions/interfaces-ethernet.xml.in
+++ b/interface-definitions/interfaces-ethernet.xml.in
@@ -75,7 +75,7 @@
</children>
</node>
#include <include/interface-mac.xml.i>
- #include <include/interface-mtu-68-9000.xml.i>
+ #include <include/interface-mtu-68-16000.xml.i>
<node name="offload-options">
<properties>
<help>Configurable offload options</help>
diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in
index 320dfd64d..25bf12bfe 100644
--- a/interface-definitions/interfaces-geneve.xml.in
+++ b/interface-definitions/interfaces-geneve.xml.in
@@ -35,7 +35,7 @@
</children>
</node>
#include <include/interface-mac.xml.i>
- #include <include/interface-mtu-1450-9000.xml.i>
+ #include <include/interface-mtu-1450-16000.xml.i>
<leafNode name="remote">
<properties>
<help>Remote address of GENEVE tunnel</help>
diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in
index 3a878ad76..c347e0926 100644
--- a/interface-definitions/interfaces-l2tpv3.xml.in
+++ b/interface-definitions/interfaces-l2tpv3.xml.in
@@ -76,7 +76,7 @@
</constraint>
</properties>
</leafNode>
- #include <include/interface-mtu-68-9000.xml.i>
+ #include <include/interface-mtu-68-16000.xml.i>
<leafNode name="peer-session-id">
<properties>
<help>Peer session identifier</help>
diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in
index 068e31449..a4035ea1f 100644
--- a/interface-definitions/interfaces-macsec.xml.in
+++ b/interface-definitions/interfaces-macsec.xml.in
@@ -107,7 +107,7 @@
</node>
#include <include/interface-description.xml.i>
#include <include/interface-disable.xml.i>
- #include <include/interface-mtu-68-9000.xml.i>
+ #include <include/interface-mtu-68-16000.xml.i>
#include <include/source-interface-ethernet.xml.i>
#include <include/interface-vrf.xml.i>
</children>
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index 5675379d5..56a35e537 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -36,7 +36,7 @@
#include <include/interface-description.xml.i>
<leafNode name="device-type">
<properties>
- <help>OpenVPN interface device-type</help>
+ <help>OpenVPN interface device-type (default: tun)</help>
<completionHelp>
<list>tun tap</list>
</completionHelp>
@@ -49,9 +49,10 @@
<description>TAP device, required for OSI layer 2</description>
</valueHelp>
<constraint>
- <regex>(tun|tap)</regex>
+ <regex>^(tun|tap)$</regex>
</constraint>
</properties>
+ <defaultValue>tun</defaultValue>
</leafNode>
#include <include/interface-disable.xml.i>
<node name="encryption">
@@ -110,7 +111,7 @@
<description>AES algorithm with 256-bit key GCM</description>
</valueHelp>
<constraint>
- <regex>(none|des|3des|bf128|bf256|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex>
+ <regex>^(none|des|3des|bf128|bf256|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)$</regex>
</constraint>
</properties>
</leafNode>
@@ -157,7 +158,7 @@
<description>AES algorithm with 256-bit key GCM</description>
</valueHelp>
<constraint>
- <regex>(none|des|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex>
+ <regex>^(none|des|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)$</regex>
</constraint>
<multi/>
</properties>
@@ -204,7 +205,7 @@
<description>SHA-512 algorithm</description>
</valueHelp>
<constraint>
- <regex>(md5|sha1|sha256|sha384|sha512)</regex>
+ <regex>^(md5|sha1|sha256|sha384|sha512)$</regex>
</constraint>
</properties>
</leafNode>
@@ -215,7 +216,7 @@
<children>
<leafNode name="failure-count">
<properties>
- <help>Maximum number of keepalive packet failures [default 6]</help>
+ <help>Maximum number of keepalive packet failures (default: 60)</help>
<valueHelp>
<format>0-1000</format>
<description>Maximum number of keepalive packet failures</description>
@@ -224,10 +225,11 @@
<validator name="numeric" argument="--range 0-1000"/>
</constraint>
</properties>
+ <defaultValue>60</defaultValue>
</leafNode>
<leafNode name="interval">
<properties>
- <help>Keepalive packet interval (seconds) [default 10]</help>
+ <help>Keepalive packet interval in seconds (default: 10)</help>
<valueHelp>
<format>0-600</format>
<description>Keepalive packet interval (seconds)</description>
@@ -236,6 +238,7 @@
<validator name="numeric" argument="--range 0-600"/>
</constraint>
</properties>
+ <defaultValue>10</defaultValue>
</leafNode>
</children>
</node>
@@ -304,7 +307,7 @@
<description>Server in client-server mode</description>
</valueHelp>
<constraint>
- <regex>(site-to-site|client|server)</regex>
+ <regex>^(site-to-site|client|server)$</regex>
</constraint>
</properties>
</leafNode>
@@ -342,9 +345,10 @@
<description>TCP and initiates connections actively</description>
</valueHelp>
<constraint>
- <regex>(udp|tcp-passive|tcp-active)</regex>
+ <regex>^(udp|tcp-passive|tcp-active)$</regex>
</constraint>
</properties>
+ <defaultValue>udp</defaultValue>
</leafNode>
<leafNode name="remote-address">
<properties>
@@ -359,6 +363,7 @@
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
</constraint>
<multi/>
</properties>
@@ -613,6 +618,7 @@
<leafNode name="reject-unconfigured-clients">
<properties>
<help>Reject connections from clients that are not explicitly configured</help>
+ <valueless/>
</properties>
</leafNode>
<leafNode name="subnet">
@@ -634,7 +640,7 @@
</leafNode>
<leafNode name="topology">
<properties>
- <help>Topology for clients</help>
+ <help>Topology for clients (default: net30)</help>
<completionHelp>
<list>net30 point-to-point subnet</list>
</completionHelp>
@@ -651,9 +657,10 @@
<description>Subnet topology</description>
</valueHelp>
<constraint>
- <regex>(subnet|point-to-point|net30)</regex>
+ <regex>^(subnet|point-to-point|net30)$</regex>
</constraint>
</properties>
+ <defaultValue>net30</defaultValue>
</leafNode>
</children>
</node>
@@ -777,13 +784,13 @@
<description>TLS v1.2</description>
</valueHelp>
<constraint>
- <regex>(1.0|1.1|1.2)</regex>
+ <regex>^(1.0|1.1|1.2)$</regex>
</constraint>
</properties>
</leafNode>
<leafNode name="role">
<properties>
- <help>Private key for this host</help>
+ <help>TLS negotiation role</help>
<completionHelp>
<list>active passive</list>
</completionHelp>
@@ -793,10 +800,10 @@
</valueHelp>
<valueHelp>
<format>passive</format>
- <description>Waiting for TLS connections passively</description>
+ <description>Wait for incoming TLS connection</description>
</valueHelp>
<constraint>
- <regex>(active|passive)</regex>
+ <regex>^(active|passive)$</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in
index 3fceb70b6..cebc244db 100644
--- a/interface-definitions/interfaces-pseudo-ethernet.xml.in
+++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in
@@ -73,7 +73,7 @@
</properties>
<defaultValue>private</defaultValue>
</leafNode>
- #include <include/interface-mtu-68-9000.xml.i>
+ #include <include/interface-mtu-68-16000.xml.i>
#include <include/vif-s.xml.i>
#include <include/vif.xml.i>
</children>
diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index 7fdead16a..37a35a1f7 100644
--- a/interface-definitions/interfaces-vxlan.xml.in
+++ b/interface-definitions/interfaces-vxlan.xml.in
@@ -67,7 +67,7 @@
</leafNode>
#include <include/source-interface.xml.i>
#include <include/interface-mac.xml.i>
- #include <include/interface-mtu-1200-9000.xml.i>
+ #include <include/interface-mtu-1200-16000.xml.i>
<leafNode name="remote">
<properties>
<help>Remote address of VXLAN tunnel</help>
diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in
index 981bce826..aa63e4ac7 100644
--- a/interface-definitions/interfaces-wireguard.xml.in
+++ b/interface-definitions/interfaces-wireguard.xml.in
@@ -21,7 +21,7 @@
#include <include/interface-disable.xml.i>
#include <include/interface-vrf.xml.i>
#include <include/port-number.xml.i>
- #include <include/interface-mtu-68-9000.xml.i>
+ #include <include/interface-mtu-68-16000.xml.i>
<leafNode name="fwmark">
<properties>
<help>A 32-bit fwmark value set on all outgoing packets</help>
diff --git a/interface-definitions/interfaces-wirelessmodem.xml.in b/interface-definitions/interfaces-wirelessmodem.xml.in
index 96604ff00..a0e78a124 100644
--- a/interface-definitions/interfaces-wirelessmodem.xml.in
+++ b/interface-definitions/interfaces-wirelessmodem.xml.in
@@ -66,7 +66,7 @@
</properties>
</leafNode>
#include <include/interface-disable-link-detect.xml.i>
- #include <include/interface-mtu-68-9000.xml.i>
+ #include <include/interface-mtu-68-16000.xml.i>
<node name="ipv6">
<children>
#include <include/ipv6-address.xml.i>
diff --git a/interface-definitions/protocols-bgp.xml.in b/interface-definitions/protocols-bgp.xml.in
index 3a4600753..6de8017f2 100644
--- a/interface-definitions/protocols-bgp.xml.in
+++ b/interface-definitions/protocols-bgp.xml.in
@@ -282,7 +282,7 @@
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
- <regex>(en|eth|br|bond|gnv|vxlan|wg|tun)[0-9]+</regex>
+ <regex>(br|bond|dum|en|eth|gnv|lo|peth|tun|vti|vxlan|wg|wlan)[0-9]+</regex>
</constraint>
</properties>
<children>
@@ -342,7 +342,6 @@
<leafNode name="description">
<properties>
<help>Description for this neighbor</help>
- <valueless/>
</properties>
</leafNode>
<leafNode name="disable-capability-negotiation">
@@ -629,7 +628,11 @@
<help>Source IP of routing updates</help>
<valueHelp>
<format>ipv4</format>
- <description>IP address of route source</description>
+ <description>IPv4 address of route source</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address of route source</description>
</valueHelp>
<valueHelp>
<format>&lt;interface&gt;</format>
@@ -637,7 +640,8 @@
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
- <regex>(en|eth|br|bond|gnv|vxlan|wg|tun)[0-9]+</regex>
+ <validator name="ipv6-address"/>
+ <regex>(br|bond|dum|en|eth|gnv|lo|peth|tun|vti|vxlan|wg|wlan)[0-9]+</regex>
</constraint>
</properties>
</leafNode>
@@ -916,12 +920,6 @@
</tagNode>
</children>
</node>
- <leafNode name="enforce-first-as">
- <properties>
- <help>Require first AS in the path to match peer AS number</help>
- <valueless/>
- </properties>
- </leafNode>
<node name="graceful-restart">
<properties>
<help>Graceful restart capability parameters</help>
@@ -1141,7 +1139,11 @@
<help>Source IP of routing updates</help>
<valueHelp>
<format>ipv4</format>
- <description>IP address of route source</description>
+ <description>IPv4 address of route source</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address of route source</description>
</valueHelp>
<valueHelp>
<format>&lt;interface&gt;</format>
@@ -1149,7 +1151,8 @@
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
- <regex>(en|eth|br|bond|gnv|vxlan|wg|tun)[0-9]+</regex>
+ <validator name="ipv6-address"/>
+ <regex>(br|bond|dum|en|eth|gnv|lo|peth|tun|vti|vxlan|wg|wlan)[0-9]+</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/protocols-mpls.xml.in b/interface-definitions/protocols-mpls.xml.in
index 3ea610d8b..94ece8d45 100644
--- a/interface-definitions/protocols-mpls.xml.in
+++ b/interface-definitions/protocols-mpls.xml.in
@@ -54,9 +54,9 @@
</valueHelp>
</properties>
<children>
- <leafNode name="hello-holdtime">
+ <leafNode name="hello-ipv4-holdtime">
<properties>
- <help>Hello holdtime</help>
+ <help>Hello ipv4 holdtime</help>
<valueHelp>
<format>1-65535</format>
<description>Time in seconds</description>
@@ -66,9 +66,33 @@
</constraint>
</properties>
</leafNode>
- <leafNode name="hello-interval">
+ <leafNode name="hello-ipv4-interval">
<properties>
- <help>Hello interval</help>
+ <help>Hello ipv4 interval</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Time in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="hello-ipv6-holdtime">
+ <properties>
+ <help>Hello ipv6 holdtime</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Time in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="hello-ipv6-interval">
+ <properties>
+ <help>Hello ipv6 interval</help>
<valueHelp>
<format>1-65535</format>
<description>Time in seconds</description>
diff --git a/interface-definitions/system-ipv6.xml.in b/interface-definitions/system-ipv6.xml.in
index 47fbeb4e1..6ead747a1 100644
--- a/interface-definitions/system-ipv6.xml.in
+++ b/interface-definitions/system-ipv6.xml.in
@@ -22,7 +22,7 @@
</leafNode>
<node name="multipath">
<properties>
- <help>IPv4 multipath settings</help>
+ <help>IPv6 multipath settings</help>
</properties>
<children>
<leafNode name="layer4-hashing">
diff --git a/interface-definitions/vpn_l2tp.xml.in b/interface-definitions/vpn_l2tp.xml.in
index 8802c0564..82af86470 100644
--- a/interface-definitions/vpn_l2tp.xml.in
+++ b/interface-definitions/vpn_l2tp.xml.in
@@ -225,6 +225,7 @@
<children>
<tagNode name="server">
<children>
+ #include <include/accel-radius-additions-disable-accounting.xlm.in>
<leafNode name="fail-time">
<properties>
<help>Mark server unavailable for &lt;n&gt; seconds on failure</help>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 62df3334c..e43b68f6f 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -18,7 +18,7 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion.
"""
import os
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
from vyos.xml import defaults
from vyos import ConfigError
@@ -174,10 +174,10 @@ def T2665_set_dhcpv6pd_defaults(config_dict):
pd_defaults = defaults(['interfaces', 'ethernet', 'dhcpv6-options', 'pd'])
# Implant default dictionary for DHCPv6-PD instances
- if vyos_dict_search('dhcpv6_options.pd.length', config_dict):
+ if dict_search('dhcpv6_options.pd.length', config_dict):
del config_dict['dhcpv6_options']['pd']['length']
- for pd in (vyos_dict_search('dhcpv6_options.pd', config_dict) or []):
+ for pd in (dict_search('dhcpv6_options.pd', config_dict) or []):
config_dict['dhcpv6_options']['pd'][pd] = dict_merge(pd_defaults,
config_dict['dhcpv6_options']['pd'][pd])
@@ -332,7 +332,7 @@ def get_interface_dict(config, base, ifname=''):
eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64'])
if eui64:
- tmp = vyos_dict_search('ipv6.address', dict)
+ tmp = dict_search('ipv6.address', dict)
if not tmp:
dict.update({'ipv6': {'address': {'eui64_old': eui64}}})
else:
@@ -419,12 +419,12 @@ def get_accel_dict(config, base, chap_secrets):
# 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):
+ if dict_search('authentication.radius.server', default_values):
del default_values['authentication']['radius']['server']
# defaults include static-ip address per TAG node which need to be added to
# individual local users instead - so we can simply delete them
- if vyos_dict_search('authentication.local_users.username', default_values):
+ if dict_search('authentication.local_users.username', default_values):
del default_values['authentication']['local_users']['username']
dict = dict_merge(default_values, dict)
@@ -448,18 +448,23 @@ def get_accel_dict(config, base, chap_secrets):
del dict['name_server']
# Add individual RADIUS server default values
- if vyos_dict_search('authentication.radius.server', dict):
+ if dict_search('authentication.radius.server', dict):
default_values = defaults(base + ['authentication', 'radius', 'server'])
- for server in vyos_dict_search('authentication.radius.server', dict):
+ for server in dict_search('authentication.radius.server', dict):
dict['authentication']['radius']['server'][server] = dict_merge(
default_values, dict['authentication']['radius']['server'][server])
+ # Check option "disable-accounting" per server and replace default value from '1813' to '0'
+ # set vpn sstp authentication radius server x.x.x.x disable-accounting
+ if 'disable_accounting' in dict['authentication']['radius']['server'][server]:
+ dict['authentication']['radius']['server'][server]['acct_port'] = '0'
+
# Add individual local-user default values
- if vyos_dict_search('authentication.local_users.username', dict):
+ if 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):
+ for username in dict_search('authentication.local_users.username', dict):
dict['authentication']['local_users']['username'][username] = dict_merge(
default_values, dict['authentication']['local_users']['username'][username])
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index 6e4214360..82b9355a3 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -27,6 +27,7 @@ COMMIT = '/opt/vyatta/sbin/my_commit'
DISCARD = '/opt/vyatta/sbin/my_discard'
SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig']
LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile']
+MIGRATE_LOAD_CONFIG = ['/usr/libexec/vyos/vyos-load-config.py']
SAVE_CONFIG = ['/opt/vyatta/sbin/vyatta-save-config.pl']
INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image', '--url']
REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del']
@@ -176,6 +177,10 @@ class ConfigSession(object):
out = self.__run_command(LOAD_CONFIG + [file_path])
return out
+ def migrate_and_load_config(self, file_path):
+ out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path])
+ return out
+
def save_config(self, file_path):
out = self.__run_command(SAVE_CONFIG + [file_path])
return out
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 422483663..babb0feb7 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -22,7 +22,7 @@
# makes use of it!
from vyos import ConfigError
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
def verify_mtu(config):
"""
@@ -60,19 +60,19 @@ def verify_mtu_ipv6(config):
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)
-
- for address in (vyos_dict_search('address', config) or []):
+ for address in (dict_search('address', config) or []):
if address in ['dhcpv6'] or is_ipv6(address):
raise ConfigError(error_msg)
- if vyos_dict_search('ipv6.address.autoconf', config):
- raise ConfigError(error_msg)
+ tmp = dict_search('ipv6.address', config)
+ if tmp and 'no_default_link_local' not in tmp:
+ raise ConfigError('link-local ' + error_msg)
- if vyos_dict_search('ipv6.address.eui64', config):
+ if tmp and 'autoconf' in tmp:
raise ConfigError(error_msg)
+ if tmp and 'eui64' in tmp:
+ raise ConfigError(error_msg)
def verify_vrf(config):
"""
@@ -154,7 +154,7 @@ def verify_dhcpv6(config):
recurring validation of DHCPv6 options which are mutually exclusive.
"""
if 'dhcpv6_options' in config:
- from vyos.util import vyos_dict_search
+ from vyos.util import dict_search
if {'parameters_only', 'temporary'} <= set(config['dhcpv6_options']):
raise ConfigError('DHCPv6 temporary and parameters-only options '
@@ -162,15 +162,15 @@ def verify_dhcpv6(config):
# It is not allowed to have duplicate SLA-IDs as those identify an
# assigned IPv6 subnet from a delegated prefix
- for pd in vyos_dict_search('dhcpv6_options.pd', config):
+ for pd in dict_search('dhcpv6_options.pd', config):
sla_ids = []
- if not vyos_dict_search(f'dhcpv6_options.pd.{pd}.interface', config):
+ if not dict_search(f'dhcpv6_options.pd.{pd}.interface', config):
raise ConfigError('DHCPv6-PD requires an interface where to assign '
'the delegated prefix!')
- for interface in vyos_dict_search(f'dhcpv6_options.pd.{pd}.interface', config):
- sla_id = vyos_dict_search(
+ for interface in dict_search(f'dhcpv6_options.pd.{pd}.interface', config):
+ sla_id = dict_search(
f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id', config)
sla_ids.append(sla_id)
@@ -211,11 +211,11 @@ def verify_accel_ppp_base_service(config):
on get_config_dict()
"""
# vertify auth settings
- if vyos_dict_search('authentication.mode', config) == 'local':
- if not vyos_dict_search('authentication.local_users', config):
+ if dict_search('authentication.mode', config) == 'local':
+ if not 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):
+ for user in dict_search('authentication.local_users.username', config):
user_config = config['authentication']['local_users']['username'][user]
if 'password' not in user_config:
@@ -227,11 +227,11 @@ def verify_accel_ppp_base_service(config):
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):
+ elif dict_search('authentication.mode', config) == 'radius':
+ if not 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):
+ for server in 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}"')
@@ -259,3 +259,27 @@ def verify_accel_ppp_base_service(config):
if 'delegation_prefix' not in ipv6_pool['delegate'][delegate]:
raise ConfigError('delegation-prefix length required!')
+def verify_diffie_hellman_length(file, min_keysize):
+ """ Verify Diffie-Hellamn keypair length given via file. It must be greater
+ then or equal to min_keysize """
+
+ try:
+ keysize = str(min_keysize)
+ except:
+ return False
+
+ import os
+ import re
+ from vyos.util import cmd
+
+ if os.path.exists(file):
+
+ out = cmd(f'openssl dhparam -inform PEM -in {file} -text')
+ prog = re.compile('\d+\s+bit')
+ if prog.search(out):
+ bits = prog.search(out)[0].split()[0]
+ if int(min_keysize) >= int(bits):
+ return True
+
+ return False
+
diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py
index 9108fc180..709222b09 100644
--- a/python/vyos/ifconfig/bond.py
+++ b/python/vyos/ifconfig/bond.py
@@ -17,7 +17,7 @@ import os
from vyos.ifconfig.interface import Interface
from vyos.util import cmd
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
from vyos.validate import assert_list
from vyos.validate import assert_positive
@@ -360,7 +360,7 @@ class BondIf(Interface):
self.set_arp_ip_target('-' + addr)
# Add configured ARP target addresses
- value = vyos_dict_search('arp_monitor.target', config)
+ value = dict_search('arp_monitor.target', config)
if isinstance(value, str):
value = [value]
if value:
@@ -384,7 +384,7 @@ class BondIf(Interface):
# Removing an interface from a bond will always place the underlaying
# physical interface in admin-down state! If physical interface is
# not disabled, re-enable it.
- if not vyos_dict_search(f'member.interface_remove.{interface}.disable', config):
+ if not dict_search(f'member.interface_remove.{interface}.disable', config):
Interface(interface).set_admin_state('up')
# Bonding policy/mode
@@ -392,7 +392,7 @@ class BondIf(Interface):
if value: self.set_mode(value)
# Add (enslave) interfaces to bond
- value = vyos_dict_search('member.interface', config)
+ value = dict_search('member.interface', config)
for interface in (value or []):
# if we've come here we already verified the interface
# does not have an addresses configured so just flush
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
index bf78f8972..f7388b298 100644
--- a/python/vyos/ifconfig/bridge.py
+++ b/python/vyos/ifconfig/bridge.py
@@ -19,7 +19,7 @@ from vyos.ifconfig.interface import Interface
from vyos.validate import assert_boolean
from vyos.validate import assert_positive
from vyos.util import cmd
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
@Interface.register
class BridgeIf(Interface):
@@ -223,17 +223,17 @@ class BridgeIf(Interface):
self.set_stp(value)
# enable or disable IGMP querier
- tmp = vyos_dict_search('igmp.querier', config)
+ tmp = dict_search('igmp.querier', config)
value = '1' if (tmp != None) else '0'
self.set_multicast_querier(value)
# remove interface from bridge
- tmp = vyos_dict_search('member.interface_remove', config)
+ tmp = dict_search('member.interface_remove', config)
for member in (tmp or []):
if member in interfaces():
self.del_port(member)
- tmp = vyos_dict_search('member.interface', config)
+ tmp = dict_search('member.interface', config)
if tmp:
for interface, interface_config in tmp.items():
# if interface does yet not exist bail out early and
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 1d48941f9..12d1ec265 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -19,7 +19,7 @@ import re
from vyos.ifconfig.interface import Interface
from vyos.validate import assert_list
from vyos.util import run
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
@Interface.register
class EthernetIf(Interface):
@@ -282,27 +282,27 @@ class EthernetIf(Interface):
self.set_flow_control(value)
# GRO (generic receive offload)
- tmp = vyos_dict_search('offload_options.generic_receive', config)
+ tmp = dict_search('offload_options.generic_receive', config)
value = tmp if (tmp != None) else 'off'
self.set_gro(value)
# GSO (generic segmentation offload)
- tmp = vyos_dict_search('offload_options.generic_segmentation', config)
+ tmp = dict_search('offload_options.generic_segmentation', config)
value = tmp if (tmp != None) else 'off'
self.set_gso(value)
# scatter-gather option
- tmp = vyos_dict_search('offload_options.scatter_gather', config)
+ tmp = dict_search('offload_options.scatter_gather', config)
value = tmp if (tmp != None) else 'off'
self.set_sg(value)
# TSO (TCP segmentation offloading)
- tmp = vyos_dict_search('offload_options.udp_fragmentation', config)
+ tmp = dict_search('offload_options.udp_fragmentation', config)
value = tmp if (tmp != None) else 'off'
self.set_tso(value)
# UDP fragmentation offloading
- tmp = vyos_dict_search('offload_options.udp_fragmentation', config)
+ tmp = dict_search('offload_options.udp_fragmentation', config)
value = tmp if (tmp != None) else 'off'
self.set_ufo(value)
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index ae747e87c..894410871 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -34,7 +34,7 @@ from vyos.configdict import list_diff
from vyos.configdict import dict_merge
from vyos.template import render
from vyos.util import mac2eui64
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
from vyos.validate import is_ipv4
from vyos.validate import is_ipv6
from vyos.validate import is_intf_addr_assigned
@@ -880,7 +880,7 @@ class Interface(Control):
lease_file = f'{config_base}_{ifname}.leases'
if enable and 'disable' not in self._config:
- if vyos_dict_search('dhcp_options.host_name', self._config) == None:
+ if dict_search('dhcp_options.host_name', self._config) == None:
# read configured system hostname.
# maybe change to vyos hostd client ???
hostname = 'vyos'
@@ -959,7 +959,7 @@ class Interface(Control):
# always ensure DHCPv6 client is stopped (when not configured as client
# for IPv6 address or prefix delegation
- dhcpv6pd = vyos_dict_search('dhcpv6_options.pd', config)
+ dhcpv6pd = dict_search('dhcpv6_options.pd', config)
if 'dhcpv6' not in new_addr or dhcpv6pd == None:
self.del_addr('dhcpv6')
@@ -987,64 +987,64 @@ class Interface(Control):
self.set_vrf(config.get('vrf', ''))
# Configure ARP cache timeout in milliseconds - has default value
- tmp = vyos_dict_search('ip.arp_cache_timeout', config)
+ tmp = dict_search('ip.arp_cache_timeout', config)
value = tmp if (tmp != None) else '30'
self.set_arp_cache_tmo(value)
# Configure ARP filter configuration
- tmp = vyos_dict_search('ip.disable_arp_filter', config)
+ tmp = dict_search('ip.disable_arp_filter', config)
value = '0' if (tmp != None) else '1'
self.set_arp_filter(value)
# Configure ARP accept
- tmp = vyos_dict_search('ip.enable_arp_accept', config)
+ tmp = dict_search('ip.enable_arp_accept', config)
value = '1' if (tmp != None) else '0'
self.set_arp_accept(value)
# Configure ARP announce
- tmp = vyos_dict_search('ip.enable_arp_announce', config)
+ tmp = dict_search('ip.enable_arp_announce', config)
value = '1' if (tmp != None) else '0'
self.set_arp_announce(value)
# Configure ARP ignore
- tmp = vyos_dict_search('ip.enable_arp_ignore', config)
+ tmp = dict_search('ip.enable_arp_ignore', config)
value = '1' if (tmp != None) else '0'
self.set_arp_ignore(value)
# Enable proxy-arp on this interface
- tmp = vyos_dict_search('ip.enable_proxy_arp', config)
+ tmp = dict_search('ip.enable_proxy_arp', config)
value = '1' if (tmp != None) else '0'
self.set_proxy_arp(value)
# Enable private VLAN proxy ARP on this interface
- tmp = vyos_dict_search('ip.proxy_arp_pvlan', config)
+ tmp = dict_search('ip.proxy_arp_pvlan', config)
value = '1' if (tmp != None) else '0'
self.set_proxy_arp_pvlan(value)
# IPv4 forwarding
- tmp = vyos_dict_search('ip.disable_forwarding', config)
+ tmp = 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)
+ tmp = dict_search('ipv6.disable_forwarding', config)
value = '0' if (tmp != None) else '1'
self.set_ipv6_forwarding(value)
# IPv6 router advertisements
- tmp = vyos_dict_search('ipv6.address.autoconf', config)
+ tmp = dict_search('ipv6.address.autoconf', config)
value = '2' if (tmp != None) else '1'
if 'dhcpv6' in new_addr:
value = '2'
self.set_ipv6_accept_ra(value)
# IPv6 address autoconfiguration
- tmp = vyos_dict_search('ipv6.address.autoconf', config)
+ tmp = dict_search('ipv6.address.autoconf', config)
value = '1' if (tmp != None) else '0'
self.set_ipv6_autoconf(value)
# IPv6 Duplicate Address Detection (DAD) tries
- tmp = vyos_dict_search('ipv6.dup_addr_detect_transmits', config)
+ tmp = dict_search('ipv6.dup_addr_detect_transmits', config)
value = tmp if (tmp != None) else '1'
self.set_ipv6_dad_messages(value)
@@ -1053,7 +1053,7 @@ class Interface(Control):
self.set_mtu(config.get('mtu'))
# Delete old IPv6 EUI64 addresses before changing MAC
- tmp = vyos_dict_search('ipv6.address.eui64_old', config)
+ tmp = dict_search('ipv6.address.eui64_old', config)
if tmp:
for addr in tmp:
self.del_ipv6_eui64_address(addr)
@@ -1068,7 +1068,7 @@ class Interface(Control):
self.set_mac(mac)
# Manage IPv6 link-local addresses
- tmp = vyos_dict_search('ipv6.address.no_default_link_local', config)
+ tmp = dict_search('ipv6.address.no_default_link_local', config)
# we must check explicitly for None type as if the key is set we will
# get an empty dict (<class 'dict'>)
if tmp is not None:
@@ -1077,7 +1077,7 @@ class Interface(Control):
self.add_ipv6_eui64_address('fe80::/64')
# Add IPv6 EUI-based addresses
- tmp = vyos_dict_search('ipv6.address.eui64', config)
+ tmp = dict_search('ipv6.address.eui64', config)
if tmp:
for addr in tmp:
self.add_ipv6_eui64_address(addr)
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py
index 964ffe383..4122d1a2f 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -179,7 +179,7 @@ class GRETapIf(_Tunnel):
default = {'type': 'gretap'}
required = ['local', ]
- options = ['local', 'remote', ]
+ options = ['local', 'remote', 'ttl',]
updates = ['mtu', ]
create = 'ip link add {ifname} type {type}'
diff --git a/python/vyos/ifconfig/vtun.py b/python/vyos/ifconfig/vtun.py
index b25e32d63..99a592b3e 100644
--- a/python/vyos/ifconfig/vtun.py
+++ b/python/vyos/ifconfig/vtun.py
@@ -19,6 +19,7 @@ from vyos.ifconfig.interface import Interface
class VTunIf(Interface):
default = {
'type': 'vtun',
+ 'device_type': 'tun',
}
definition = {
**Interface.definition,
@@ -28,15 +29,44 @@ class VTunIf(Interface):
'bridgeable': True,
},
}
-
- # stub this interface is created in the configure script
+ options = Interface.options + ['device_type']
def _create(self):
- # we can not create this interface as it is managed outside
- # it requires configuring OpenVPN
+ """ Depending on OpenVPN operation mode the interface is created
+ immediately (e.g. Server mode) or once the connection to the server is
+ established (client mode). The latter will only be brought up once the
+ server can be reached, thus we might need to create this interface in
+ advance for the service to be operational. """
+ try:
+ cmd = 'openvpn --mktun --dev-type {device_type} --dev {ifname}'.format(**self.config)
+ return self._cmd(cmd)
+ except PermissionError:
+ # interface created by OpenVPN daemon in the meantime ...
+ pass
+
+ def add_addr(self, addr):
+ # IP addresses are managed by OpenVPN daemon
pass
- def _delete(self):
- # we can not create this interface as it is managed outside
- # it requires configuring OpenVPN
+ def del_addr(self, addr):
+ # IP addresses are managed by OpenVPN daemon
pass
+
+ def update(self, config):
+ """ General helper function which works on a dictionary retrived by
+ get_config_dict(). It's main intention is to consolidate the scattered
+ interface setup code and provide a single point of entry when workin
+ on any interface. """
+
+ # call base class first
+ super().update(config)
+
+ # Enable/Disable of an interface must always be done at the end of the
+ # derived class to make use of the ref-counting set_admin_state()
+ # function. We will only enable the interface if 'up' was called as
+ # often as 'down'. This is required by some interface implementations
+ # as certain parameters can only be changed when the interface is
+ # in admin-down state. This ensures the link does not flap during
+ # reconfiguration.
+ state = 'down' if 'disable' in config else 'up'
+ self.set_admin_state(state)
diff --git a/python/vyos/template.py b/python/vyos/template.py
index c88ab04a0..389f6927f 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -16,7 +16,6 @@
import functools
import os
-from ipaddress import ip_network
from jinja2 import Environment
from jinja2 import FileSystemLoader
from vyos.defaults import directories
@@ -124,20 +123,78 @@ def render(
# Custom template filters follow #
##################################
-
-@register_filter("address_from_cidr")
+@register_filter('address_from_cidr')
def vyos_address_from_cidr(text):
""" Take an IPv4/IPv6 CIDR prefix and convert the network to an "address".
Example:
192.0.2.0/24 -> 192.0.2.0, 2001:db8::/48 -> 2001:db8::
"""
- return ip_network(text).network_address
-
+ from ipaddress import ip_network
+ return str(ip_network(text).network_address)
-@register_filter("netmask_from_cidr")
+@register_filter('netmask_from_cidr')
def vyos_netmask_from_cidr(text):
- """ Take an IPv4/IPv6 CIDR prefix and convert the prefix length to a "subnet mask".
+ """ Take CIDR prefix and convert the prefix length to a "subnet mask".
+ Example:
+ - 192.0.2.0/24 -> 255.255.255.0
+ - 2001:db8::/48 -> ffff:ffff:ffff::
+ """
+ from ipaddress import ip_network
+ return str(ip_network(text).netmask)
+
+@register_filter('ipv4')
+def vyos_ipv4(text):
+ """ Filter IP address, return True on IPv4 address, False otherwise """
+ from ipaddress import ip_interface
+ try: return ip_interface(text).version == 4
+ except: return False
+
+@register_filter('ipv6')
+def vyos_ipv6(text):
+ """ Filter IP address, return True on IPv6 address, False otherwise """
+ from ipaddress import ip_interface
+ try: return ip_interface(text).version == 6
+ except: return False
+
+@register_filter('first_host_address')
+def vyos_first_host_address(text):
+ """ Return first usable (host) IP address from given prefix.
+ Example:
+ - 10.0.0.0/24 -> 10.0.0.1
+ - 2001:db8::/64 -> 2001:db8::
+ """
+ from ipaddress import ip_interface
+ from ipaddress import IPv4Network
+ from ipaddress import IPv6Network
+
+ addr = ip_interface(text)
+ if addr.version == 4:
+ return str(addr.ip +1)
+ return str(addr.ip)
+
+@register_filter('last_host_address')
+def vyos_last_host_address(text):
+ """ Return first usable IP address from given prefix.
+ Example:
+ - 10.0.0.0/24 -> 10.0.0.254
+ - 2001:db8::/64 -> 2001:db8::ffff:ffff:ffff:ffff
+ """
+ from ipaddress import ip_interface
+ from ipaddress import IPv4Network
+ from ipaddress import IPv6Network
+
+ addr = ip_interface(text)
+ if addr.version == 4:
+ return str(IPv4Network(addr).broadcast_address - 1)
+
+ return str(IPv6Network(addr).broadcast_address)
+
+@register_filter('inc_ip')
+def vyos_inc_ip(text, increment):
+ """ Return first usable IP address from given prefix.
Example:
- 192.0.2.0/24 -> 255.255.255.0, 2001:db8::/48 -> ffff:ffff:ffff::
+ - 10.0.0.0/24 -> 10.0.0.1
+ - 2001:db8::/64 -> 2001:db8::1
"""
- return ip_network(text).netmask
+ from ipaddress import ip_interface
+ return str(ip_interface(text).ip + int(increment))
diff --git a/python/vyos/util.py b/python/vyos/util.py
index b5f0ea36e..fc6915687 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -581,77 +581,6 @@ def get_half_cpus():
cpu /= 2
return int(cpu)
-def ifname_from_config(conf):
- """
- Gets interface name with VLANs from current config level.
- Level must be at the interface whose name we want.
-
- Example:
- >>> from vyos.util import ifname_from_config
- >>> from vyos.config import Config
- >>> conf = Config()
- >>> conf.set_level('interfaces ethernet eth0 vif-s 1 vif-c 2')
- >>> ifname_from_config(conf)
- 'eth0.1.2'
- """
- level = conf.get_level()
-
- # vlans
- if level[-2] == 'vif' or level[-2] == 'vif-s':
- return level[-3] + '.' + level[-1]
- if level[-2] == 'vif-c':
- return level[-5] + '.' + level[-3] + '.' + level[-1]
-
- # no vlans
- return level[-1]
-
-def get_bridge_member_config(conf, br, intf):
- """
- Gets bridge port (member) configuration
-
- Arguments:
- conf: Config
- br: bridge name
- intf: interface name
-
- Returns:
- dict with the configuration
- False if bridge or bridge port doesn't exist
- """
- old_level = conf.get_level()
- conf.set_level([])
-
- bridge = f'interfaces bridge {br}'
- member = f'{bridge} member interface {intf}'
- if not ( conf.exists(bridge) and conf.exists(member) ):
- return False
-
- # default bridge port configuration
- # cost and priority initialized with linux defaults
- # by reading /sys/devices/virtual/net/br0/brif/eth2/{path_cost,priority}
- # after adding interface to bridge after reboot
- memberconf = {
- 'cost': 100,
- 'priority': 32,
- 'arp_cache_tmo': 30,
- 'disable_link_detect': 1,
- }
-
- if conf.exists(f'{member} cost'):
- memberconf['cost'] = int(conf.return_value(f'{member} cost'))
-
- if conf.exists(f'{member} priority'):
- memberconf['priority'] = int(conf.return_value(f'{member} priority'))
-
- if conf.exists(f'{bridge} ip arp-cache-timeout'):
- memberconf['arp_cache_tmo'] = int(conf.return_value(f'{bridge} ip arp-cache-timeout'))
-
- if conf.exists(f'{bridge} disable-link-detect'):
- memberconf['disable_link_detect'] = 2
-
- conf.set_level(old_level)
- return memberconf
-
def check_kmod(k_mod):
""" Common utility function to load required kernel modules on demand """
from vyos import ConfigError
@@ -674,7 +603,7 @@ def find_device_file(device):
return None
-def vyos_dict_search(path, dict):
+def dict_search(path, dict):
""" Traverse Python dictionary (dict) delimited by dot (.).
Return value of key if found, None otherwise.
diff --git a/python/vyos/validate.py b/python/vyos/validate.py
index 691cf3c8e..74488bed6 100644
--- a/python/vyos/validate.py
+++ b/python/vyos/validate.py
@@ -1,4 +1,4 @@
-# Copyright 2018 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2018-2020 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -13,11 +13,7 @@
# 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/>.
-import json
-import socket
import netifaces
-import ipaddress
-
from vyos.util import cmd
# Important note when you are adding new validation functions:
@@ -29,60 +25,36 @@ from vyos.util import cmd
# parameters with default will be left unset
# all other paramters will receive the value to check
-
def is_ip(addr):
- """
- Check addr if it is an IPv4 or IPv6 address
- """
+ """ Check addr if it is an IPv4 or IPv6 address """
return is_ipv4(addr) or is_ipv6(addr)
def is_ipv4(addr):
- """
- Check addr if it is an IPv4 address/network. Returns True/False
- """
-
- # With the below statement we can check for IPv4 networks and host
- # addresses at the same time
- try:
- if ipaddress.ip_address(addr.split(r'/')[0]).version == 4:
- return True
- except:
- pass
-
- return False
+ from vyos.template import vyos_ipv4
+ return vyos_ipv4(addr)
def is_ipv6(addr):
- """
- Check addr if it is an IPv6 address/network. Returns True/False
- """
-
- # With the below statement we can check for IPv4 networks and host
- # addresses at the same time
- try:
- if ipaddress.ip_network(addr.split(r'/')[0]).version == 6:
- return True
- except:
- pass
-
- return False
+ from vyos.template import vyos_ipv6
+ return vyos_ipv6(addr)
def is_ipv6_link_local(addr):
- """
- Check addr if it is an IPv6 link-local address/network. Returns True/False
- """
-
+ """ Check if addrsss is an IPv6 link-local address. Returns True/False """
+ from ipaddress import IPv6Address
addr = addr.split('%')[0]
if is_ipv6(addr):
- if ipaddress.IPv6Address(addr).is_link_local:
+ if IPv6Address(addr).is_link_local:
return True
return False
def _are_same_ip(one, two):
+ from socket import AF_INET
+ from socket import AF_INET6
+ from socket import inet_pton
# compare the binary representation of the IP
- f_one = socket.AF_INET if is_ipv4(one) else socket.AF_INET6
- s_two = socket.AF_INET if is_ipv4(two) else socket.AF_INET6
- return socket.inet_pton(f_one, one) == socket.inet_pton(f_one, two)
+ f_one = AF_INET if is_ipv4(one) else AF_INET6
+ s_two = AF_INET if is_ipv4(two) else AF_INET6
+ return inet_pton(f_one, one) == inet_pton(f_one, two)
def is_intf_addr_assigned(intf, addr):
if '/' in addr:
@@ -149,10 +121,9 @@ def is_addr_assigned(addr):
return False
def is_loopback_addr(addr):
- """
- Check if supplied IPv4/IPv6 address is a loopback address
- """
- return ipaddress.ip_address(addr).is_loopback
+ """ Check if supplied IPv4/IPv6 address is a loopback address """
+ from ipaddress import ip_address
+ return ip_address(addr).is_loopback
def is_subnet_connected(subnet, primary=False):
"""
@@ -165,6 +136,8 @@ def is_subnet_connected(subnet, primary=False):
Return True/False
"""
+ from ipaddress import ip_address
+ from ipaddress import ip_network
# determine IP version (AF_INET or AF_INET6) depending on passed address
addr_type = netifaces.AF_INET
@@ -180,7 +153,7 @@ def is_subnet_connected(subnet, primary=False):
# only support the primary address :(
if primary:
ip = netifaces.ifaddresses(interface)[addr_type][0]['addr']
- if ipaddress.ip_address(ip) in ipaddress.ip_network(subnet):
+ if ip_address(ip) in ip_network(subnet):
return True
else:
# Check every assigned IP address if it is connected to the subnet
@@ -188,7 +161,7 @@ def is_subnet_connected(subnet, primary=False):
for ip in netifaces.ifaddresses(interface)[addr_type]:
# remove interface extension (e.g. %eth0) that gets thrown on the end of _some_ addrs
addr = ip['addr'].split('%')[0]
- if ipaddress.ip_address(addr) in ipaddress.ip_network(subnet):
+ if ip_address(addr) in ip_network(subnet):
return True
return False
@@ -224,6 +197,7 @@ def assert_positive(n, smaller=0):
def assert_mtu(mtu, ifname):
assert_number(mtu)
+ import json
out = cmd(f'ip -j -d link show dev {ifname}')
# [{"ifindex":2,"ifname":"eth0","flags":["BROADCAST","MULTICAST","UP","LOWER_UP"],"mtu":1500,"qdisc":"pfifo_fast","operstate":"UP","linkmode":"DEFAULT","group":"default","txqlen":1000,"link_type":"ether","address":"08:00:27:d9:5b:04","broadcast":"ff:ff:ff:ff:ff:ff","promiscuity":0,"min_mtu":46,"max_mtu":16110,"inet6_addr_gen_mode":"none","num_tx_queues":1,"num_rx_queues":1,"gso_max_size":65536,"gso_max_segs":65535}]
parsed = json.loads(out)[0]
diff --git a/smoketest/bin/vyos-configtest b/smoketest/bin/vyos-configtest
new file mode 100755
index 000000000..3e42b0380
--- /dev/null
+++ b/smoketest/bin/vyos-configtest
@@ -0,0 +1,82 @@
+#!/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 sys
+import time
+import logging
+import unittest
+
+from vyos.configsession import ConfigSession, ConfigSessionError
+from vyos import ConfigError
+
+config_dir = '/usr/libexec/vyos/tests/config'
+save_config = '/tmp/vyos-configtest-save'
+
+class DynamicClassBase(unittest.TestCase):
+ def setUp(self):
+ self._start_time = time.time()
+ self.session = ConfigSession(os.getpid())
+ self.session.save_config(save_config)
+
+ def tearDown(self):
+ self.session.migrate_and_load_config(save_config)
+ self.session.commit()
+ log.info(f" time: {time.time() - self._start_time:.3f}")
+ del self.session
+ try:
+ os.remove(save_config)
+ except OSError:
+ pass
+
+def make_test_function(filename):
+ def test_config_load(self):
+ config_path = os.path.join(config_dir, filename)
+ self.session.migrate_and_load_config(config_path)
+ try:
+ self.session.commit()
+ except (ConfigError, ConfigSessionError):
+ self.session.discard()
+ self.fail()
+ return test_config_load
+
+def class_name_from_func_name(s):
+ res = ''.join(str.capitalize(x) for x in s.split('_'))
+ return res
+
+if __name__ == '__main__':
+ logging.basicConfig(stream=sys.stdout, level=logging.DEBUG,
+ format='%(message)s')
+ log = logging.getLogger("TestConfigLog")
+
+ start_time = time.time()
+ log.info("Generating tests")
+
+ (_, _, config_list) = next(iter(os.walk(config_dir)))
+ config_list.sort()
+
+ for config in config_list:
+ test_func = make_test_function(config)
+
+ func_name = config.replace('-', '_')
+ klassname = f'TestConfig{class_name_from_func_name(func_name)}'
+ globals()[klassname] = type(klassname,
+ (DynamicClassBase,),
+ {f'test_{func_name}': test_func})
+
+ log.info(f"... completed: {time.time() - start_time:.6f}")
+
+ unittest.main(verbosity=2)
diff --git a/smoketest/configs/pppoe-client b/smoketest/configs/pppoe-client
new file mode 100644
index 000000000..ef6a26423
--- /dev/null
+++ b/smoketest/configs/pppoe-client
@@ -0,0 +1,62 @@
+interfaces {
+ ethernet eth0 {
+ }
+ loopback lo {
+ }
+ pppoe pppoe0 {
+ authentication {
+ password bar
+ user foo
+ }
+ connect-on-demand
+ default-route auto
+ mtu 1492
+ source-interface eth0
+ }
+}
+service {
+ ssh {
+ }
+}
+system {
+ config-management {
+ commit-revisions 100
+ }
+ console {
+ device ttyS0 {
+ speed 115200
+ }
+ }
+ host-name vyos
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/
+ plaintext-password ""
+ }
+ }
+ }
+ ntp {
+ server 0.pool.ntp.org {
+ }
+ server 1.pool.ntp.org {
+ }
+ server 2.pool.ntp.org {
+ }
+ }
+ syslog {
+ global {
+ facility all {
+ level info
+ }
+ facility protocols {
+ level debug
+ }
+ }
+ }
+}
+
+
+// Warning: Do not remove the following line.
+// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@13:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@19:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1"
+// Release version: 1.3-rolling-202010241631
diff --git a/smoketest/configs/pppoe-server b/smoketest/configs/pppoe-server
new file mode 100644
index 000000000..7e4ccc80e
--- /dev/null
+++ b/smoketest/configs/pppoe-server
@@ -0,0 +1,94 @@
+interfaces {
+ ethernet eth0 {
+ address dhcp
+ }
+ ethernet eth1 {
+ address 192.168.0.1/24
+ }
+ ethernet eth2 {
+ }
+ loopback lo {
+ }
+}
+nat {
+ source {
+ rule 100 {
+ outbound-interface eth0
+ source {
+ address 192.168.0.0/24
+ }
+ translation {
+ address masquerade
+ }
+ }
+ }
+}
+service {
+ pppoe-server {
+ access-concentrator ACN
+ authentication {
+ local-users {
+ username foo {
+ password bar
+ rate-limit {
+ download 20480
+ upload 10240
+ }
+ }
+ }
+ mode local
+ }
+ client-ip-pool {
+ start 192.168.0.100
+ stop 192.168.0.200
+ }
+ gateway-address 192.168.0.2
+ interface eth2 {
+ }
+ name-server 192.168.0.1
+ }
+ ssh {
+ }
+}
+system {
+ config-management {
+ commit-revisions 100
+ }
+ console {
+ device ttyS0 {
+ speed 115200
+ }
+ }
+ host-name vyos
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0
+ plaintext-password ""
+ }
+ }
+ }
+ ntp {
+ server 0.pool.ntp.org {
+ }
+ server 1.pool.ntp.org {
+ }
+ server 2.pool.ntp.org {
+ }
+ }
+ syslog {
+ global {
+ facility all {
+ level info
+ }
+ facility protocols {
+ level debug
+ }
+ }
+ }
+}
+
+
+// Warning: Do not remove the following line.
+// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@13:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@19:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1"
+// Release version: 1.3-rolling-202010260127
diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py
index 56cbf1dd4..e3e5071c1 100644
--- a/smoketest/scripts/cli/base_accel_ppp_test.py
+++ b/smoketest/scripts/cli/base_accel_ppp_test.py
@@ -192,3 +192,23 @@ class BasicAccelPPPTest:
# Check for running process
self.assertTrue(process_named_running(self._process_name))
+
+ #
+ # Disable Radius Accounting
+ #
+ self.delete(['authentication', 'radius', 'server', radius_server, 'acct-port'])
+ self.set(['authentication', 'radius', 'server', radius_server, 'disable-accounting'])
+
+ # commit changes
+ self.session.commit()
+
+ conf.read(self._config_file)
+
+ server = conf['radius']['server'].split(',')
+ self.assertEqual(radius_server, server[0])
+ self.assertEqual(radius_key, server[1])
+ self.assertEqual(f'auth-port={radius_port}', server[2])
+ self.assertEqual(f'acct-port=0', server[3])
+ self.assertEqual(f'req-limit=0', server[4])
+ self.assertEqual(f'fail-time=0', server[5])
+
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index c6bb5bd1a..653cc91f9 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -22,7 +22,7 @@ from vyos.configsession import ConfigSession
from vyos.ifconfig import Interface
from vyos.util import read_file
from vyos.util import cmd
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local
class BasicInterfaceTest:
@@ -162,16 +162,45 @@ class BasicInterfaceTest:
""" Testcase if MTU can be changed on interface """
if not self._test_mtu:
return None
+
+ for intf in self._interfaces:
+ base = self._base_path + [intf]
+ self.session.set(base + ['mtu', self._mtu])
+ for option in self._options.get(intf, []):
+ self.session.set(base + option.split())
+
+ # commit interface changes
+ self.session.commit()
+
+ # verify changed MTU
+ for intf in self._interfaces:
+ self._mtu_test(intf)
+
+ def test_change_mtu_1200(self):
+ """ Testcase if MTU can be changed to 1200 on non IPv6 enabled interfaces """
+ if not self._test_mtu:
+ return None
+
+ old_mtu = self._mtu
+ self._mtu = '1200'
+
for intf in self._interfaces:
base = self._base_path + [intf]
self.session.set(base + ['mtu', self._mtu])
+ self.session.set(base + ['ipv6', 'address', 'no-default-link-local'])
+
for option in self._options.get(intf, []):
self.session.set(base + option.split())
+ # commit interface changes
self.session.commit()
+
+ # verify changed MTU
for intf in self._interfaces:
self._mtu_test(intf)
+ self._mtu = old_mtu
+
def test_8021q_vlan(self):
""" Testcase for 802.1q VLAN interfaces """
if not self._test_vlan:
@@ -219,7 +248,7 @@ class BasicInterfaceTest:
for interface in self._interfaces:
for vif_s in self._qinq_range:
tmp = json.loads(cmd(f'ip -d -j link show dev {interface}.{vif_s}'))[0]
- self.assertEqual(vyos_dict_search('linkinfo.info_data.protocol', tmp), '802.1ad')
+ self.assertEqual(dict_search('linkinfo.info_data.protocol', tmp), '802.1ad')
for vif_c in self._vlan_range:
vif = f'{interface}.{vif_s}.{vif_c}'
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index 5cc62e3e2..9ffb945b9 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -26,6 +26,9 @@ from vyos.configsession import ConfigSessionError
from vyos.util import cmd
from vyos.util import process_named_running
from vyos.util import read_file
+from vyos.template import vyos_inc_ip
+from vyos.template import vyos_netmask_from_cidr
+from vyos.template import vyos_last_host_address
PROCESS_NAME = 'openvpn'
@@ -35,13 +38,15 @@ ssl_cert = '/config/auth/ovpn_test_server.pem'
ssl_key = '/config/auth/ovpn_test_server.key'
dh_pem = '/config/auth/ovpn_test_dh.pem'
s2s_key = '/config/auth/ovpn_test_site2site.key'
+auth_key = '/config/auth/ovpn_test_tls_auth.key'
remote_port = '1194'
protocol = 'udp'
path = []
interface = ''
remote_host = ''
-vrf_name = 'mgmt'
+vrf_name = 'orange'
+dummy_if = 'dum1301'
def get_vrf(interface):
for upper in glob(f'/sys/class/net/{interface}/upper*'):
@@ -54,20 +59,88 @@ def get_vrf(interface):
class TestInterfacesOpenVPN(unittest.TestCase):
def setUp(self):
self.session = ConfigSession(os.getpid())
- self.session.set(['interfaces', 'dummy', 'dum1328', 'address', '192.0.2.1/24'])
+ self.session.set(['interfaces', 'dummy', dummy_if, 'address', '192.0.2.1/32'])
self.session.set(['vrf', 'name', vrf_name, 'table', '12345'])
def tearDown(self):
self.session.delete(base_path)
- self.session.delete(['interfaces', 'dummy', 'dum1328'])
- self.session.delete(['vrf', 'name', vrf_name])
+ self.session.delete(['interfaces', 'dummy', dummy_if])
+ self.session.delete(['vrf'])
self.session.commit()
del self.session
- def test_client_interfaces(self):
- """ Create OpenVPN client interfaces connecting to different
- server IP addresses. Validate configuration afterwards. """
+ def test_client_verify(self):
+ """
+ Create OpenVPN client interface and test verify() steps.
+ """
+ interface = 'vtun2000'
+ path = base_path + [interface]
+ self.session.set(path + ['mode', 'client'])
+
+ # check validate() - cannot specify both "encryption disable-ncp" and
+ # "encryption ncp-ciphers" at the same time
+ self.session.set(path + ['encryption', 'disable-ncp'])
+ self.session.set(path + ['encryption', 'ncp-ciphers', 'aes192gcm'])
+
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['encryption', 'ncp-ciphers'])
+
+ # check validate() - cannot specify local-port in client mode
+ self.session.set(path + ['local-port', '5000'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['local-port'])
+
+ # check validate() - cannot specify local-host in client mode
+ self.session.set(path + ['local-host', '127.0.0.1'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['local-host'])
+
+ # check validate() - cannot specify protocol tcp-passive in client mode
+ self.session.set(path + ['protocol', 'tcp-passive'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['protocol'])
+
+ # check validate() - remote-host must be set in client mode
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(path + ['remote-host', '192.0.9.9'])
+
+ # check validate() - cannot specify "tls dh-file" in client mode
+ self.session.set(path + ['tls', 'dh-file', dh_pem])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['tls'])
+
+ # check validate() - must specify one of "shared-secret-key-file" and "tls"
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(path + ['shared-secret-key-file', s2s_key])
+
+ # check validate() - must specify one of "shared-secret-key-file" and "tls"
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['shared-secret-key-file', s2s_key])
+
+ 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])
+
+ # client commit must pass
+ self.session.commit()
+
+ self.assertTrue(process_named_running(PROCESS_NAME))
+ self.assertIn(interface, interfaces())
+
+ def test_client_interfaces(self):
+ """
+ Create OpenVPN client interfaces connecting to different
+ server IP addresses. Validate configuration afterwards.
+ """
num_range = range(10, 15)
for ii in num_range:
interface = f'vtun{ii}'
@@ -122,11 +195,121 @@ class TestInterfacesOpenVPN(unittest.TestCase):
interface = f'vtun{ii}'
self.assertNotIn(interface, interfaces())
+ def test_server_verify(self):
+ """
+ Create one OpenVPN server interface and check required verify() stages
+ """
+ interface = 'vtun5000'
+ path = base_path + [interface]
+
+ # check validate() - must speciy operating mode
+ self.session.set(path)
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(path + ['mode', 'server'])
+
+ # check validate() - cannot specify protocol tcp-active in server mode
+ self.session.set(path + ['protocol', 'tcp-active'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['protocol'])
+
+ # check validate() - cannot specify local-port in client mode
+ self.session.set(path + ['remote-port', '5000'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['remote-port'])
+
+ # check validate() - cannot specify local-host in client mode
+ self.session.set(path + ['remote-host', '127.0.0.1'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['remote-host'])
+
+ # check validate() - must specify "tls dh-file" when not using EC keys
+ # in server mode
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(path + ['tls', 'dh-file', dh_pem])
+
+ # check validate() - must specify "server subnet" or add interface to
+ # bridge in server mode
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+
+ # check validate() - server client-ip-pool is too large
+ # [100.64.0.4 -> 100.127.255.251 = 4194295], maximum is 65536 addresses.
+ self.session.set(path + ['server', 'subnet', '100.64.0.0/10'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+
+ # check validate() - cannot specify more than 1 IPv4 and 1 IPv6 server subnet
+ self.session.set(path + ['server', 'subnet', '100.64.0.0/20'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['server', 'subnet', '100.64.0.0/10'])
+
+ # check validate() - must specify "tls ca-cert-file"
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(path + ['tls', 'ca-cert-file', ca_cert])
+
+ # check validate() - must specify "tls cert-file"
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(path + ['tls', 'cert-file', ssl_cert])
+
+ # check validate() - must specify "tls key-file"
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(path + ['tls', 'key-file', ssl_key])
+
+ # check validate() - cannot specify "tls role" in client-server mode'
+ self.session.set(path + ['tls', 'role', 'active'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+
+ # check validate() - cannot specify "tls role" in client-server mode'
+ self.session.set(path + ['tls', 'auth-file', auth_key])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+
+ # check validate() - cannot specify "tcp-passive" when "tls role" is "active"
+ self.session.set(path + ['protocol', 'tcp-passive'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['protocol'])
+
+ # check validate() - cannot specify "tls dh-file" when "tls role" is "active"
+ self.session.set(path + ['tls', 'dh-file', dh_pem])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['tls', 'dh-file'])
+
+ # Now test the other path with tls role passive
+ self.session.set(path + ['tls', 'role', 'passive'])
+ # check validate() - cannot specify "tcp-active" when "tls role" is "passive"
+ self.session.set(path + ['protocol', 'tcp-active'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['protocol'])
+
+
+ # check validate() - must specify "tls dh-file" when "tls role" is "passive"
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(path + ['tls', 'dh-file', dh_pem])
- def test_server_interfaces(self):
- """ Create OpenVPN server interfaces using different client subnets.
- Validate configuration afterwards. """
+ self.session.commit()
+
+ self.assertTrue(process_named_running(PROCESS_NAME))
+ self.assertIn(interface, interfaces())
+ def test_server_subnet_topology(self):
+ """
+ Create OpenVPN server interfaces using different client subnets.
+ Validate configuration afterwards.
+ """
auth_hash = 'sha256'
num_range = range(20, 25)
port = ''
@@ -142,6 +325,8 @@ class TestInterfacesOpenVPN(unittest.TestCase):
self.session.set(path + ['mode', 'server'])
self.session.set(path + ['local-port', port])
self.session.set(path + ['server', 'subnet', subnet])
+ self.session.set(path + ['server', 'topology', 'subnet'])
+ self.session.set(path + ['replace-default-route'])
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])
@@ -153,6 +338,82 @@ class TestInterfacesOpenVPN(unittest.TestCase):
for ii in num_range:
interface = f'vtun{ii}'
subnet = f'192.0.{ii}.0/24'
+ start_addr = vyos_inc_ip(subnet, '2')
+ stop_addr = vyos_last_host_address(subnet)
+ port = str(2000 + ii)
+
+ config_file = f'/run/openvpn/{interface}.conf'
+ config = read_file(config_file)
+
+ self.assertIn(f'dev {interface}', config)
+ self.assertIn(f'dev-type tun', config)
+ self.assertIn(f'persist-key', config)
+ self.assertIn(f'proto udp', config) # default protocol
+ self.assertIn(f'auth {auth_hash}', config)
+ self.assertIn(f'cipher aes-192-cbc', config)
+ self.assertIn(f'topology subnet', config)
+ self.assertIn(f'lport {port}', config)
+ self.assertIn(f'push "redirect-gateway def1"', config)
+
+ # TLS options
+ self.assertIn(f'ca {ca_cert}', config)
+ self.assertIn(f'cert {ssl_cert}', config)
+ self.assertIn(f'key {ssl_key}', config)
+ self.assertIn(f'dh {dh_pem}', config)
+
+ # IP pool configuration
+ netmask = IPv4Network(subnet).netmask
+ network = IPv4Network(subnet).network_address
+ self.assertIn(f'server {network} {netmask} nopool', config)
+ self.assertIn(f'ifconfig-pool {start_addr} {stop_addr}', config)
+
+ self.assertTrue(process_named_running(PROCESS_NAME))
+ self.assertEqual(get_vrf(interface), vrf_name)
+ self.assertIn(interface, interfaces())
+
+ # check that no interface remained after deleting them
+ self.session.delete(base_path)
+ self.session.commit()
+
+ for ii in num_range:
+ interface = f'vtun{ii}'
+ self.assertNotIn(interface, interfaces())
+
+ def test_server_net30_topology(self):
+ """
+ Create OpenVPN server interfaces (net30) using different client
+ subnets. Validate configuration afterwards.
+ """
+ auth_hash = 'sha256'
+ num_range = range(20, 25)
+ port = ''
+ for ii in num_range:
+ interface = f'vtun{ii}'
+ subnet = f'192.0.{ii}.0/24'
+ path = base_path + [interface]
+ port = str(2000 + ii)
+
+ self.session.set(path + ['device-type', 'tun'])
+ self.session.set(path + ['encryption', 'cipher', 'aes192'])
+ self.session.set(path + ['hash', auth_hash])
+ self.session.set(path + ['mode', 'server'])
+ self.session.set(path + ['local-port', port])
+ self.session.set(path + ['server', 'subnet', subnet])
+ self.session.set(path + ['server', 'topology', 'net30'])
+ self.session.set(path + ['replace-default-route'])
+ self.session.set(path + ['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.set(path + ['tls', 'dh-file', dh_pem])
+ self.session.set(path + ['vrf', vrf_name])
+
+ self.session.commit()
+
+ for ii in num_range:
+ interface = f'vtun{ii}'
+ subnet = f'192.0.{ii}.0/24'
+ start_addr = vyos_inc_ip(subnet, '4')
+ stop_addr = vyos_last_host_address(subnet)
port = str(2000 + ii)
config_file = f'/run/openvpn/{interface}.conf'
@@ -166,6 +427,7 @@ class TestInterfacesOpenVPN(unittest.TestCase):
self.assertIn(f'cipher aes-192-cbc', config)
self.assertIn(f'topology net30', config)
self.assertIn(f'lport {port}', config)
+ self.assertIn(f'push "redirect-gateway def1"', config)
# TLS options
self.assertIn(f'ca {ca_cert}', config)
@@ -177,6 +439,7 @@ class TestInterfacesOpenVPN(unittest.TestCase):
netmask = IPv4Network(subnet).netmask
network = IPv4Network(subnet).network_address
self.assertIn(f'server {network} {netmask} nopool', config)
+ self.assertIn(f'ifconfig-pool {start_addr} {stop_addr}', config)
self.assertTrue(process_named_running(PROCESS_NAME))
self.assertEqual(get_vrf(interface), vrf_name)
@@ -190,9 +453,69 @@ class TestInterfacesOpenVPN(unittest.TestCase):
interface = f'vtun{ii}'
self.assertNotIn(interface, interfaces())
+ def test_site2site_verify(self):
+ """
+ Create one OpenVPN site2site interface and check required verify() stages
+ """
+ interface = 'vtun5000'
+ path = base_path + [interface]
+
+ self.session.set(path + ['mode', 'site-to-site'])
+
+ # check validate() - encryption ncp-ciphers cannot be specified in site-to-site mode
+ self.session.set(path + ['encryption', 'ncp-ciphers', 'aes192gcm'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['encryption'])
+
+ # check validate() - must specify "local-address" or add interface to bridge
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(path + ['local-address', '10.0.0.1'])
+ self.session.set(path + ['local-address', '2001:db8:1::1'])
+
+ # check validate() - cannot specify more than 1 IPv4 local-address
+ self.session.set(path + ['local-address', '10.0.0.2'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['local-address', '10.0.0.2'])
+
+ # check validate() - cannot specify more than 1 IPv6 local-address
+ self.session.set(path + ['local-address', '2001:db8:1::2'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['local-address', '2001:db8:1::2'])
+
+ # check validate() - IPv4 "local-address" requires IPv4 "remote-address"
+ # or IPv4 "local-address subnet"
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(path + ['remote-address', '192.168.0.1'])
+ self.session.set(path + ['remote-address', '2001:db8:ffff::1'])
+
+ # check validate() - Cannot specify more than 1 IPv4 "remote-address"
+ self.session.set(path + ['remote-address', '192.168.0.2'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['remote-address', '192.168.0.2'])
+
+ # check validate() - Cannot specify more than 1 IPv6 "remote-address"
+ self.session.set(path + ['remote-address', '2001:db8:ffff::2'])
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.delete(path + ['remote-address', '2001:db8:ffff::2'])
+
+ # check validate() - Must specify one of "shared-secret-key-file" and "tls"
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.session.set(path + ['shared-secret-key-file', s2s_key])
+
+ self.session.commit()
def test_site2site_interfaces(self):
- """ Create two OpenVPN site-to-site interfaces """
+ """
+ Create two OpenVPN site-to-site interfaces
+ """
num_range = range(30, 35)
port = ''
local_address = ''
@@ -250,32 +573,33 @@ if __name__ == '__main__':
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) and not os.path.isfile(dh_pem) and
- not os.path.isfile(s2s_key)):
-
+ if not (os.path.isfile(ssl_key) and os.path.isfile(ssl_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}'
- out = cmd(tmp)
- print(out)
+ print(cmd(tmp))
+ if not os.path.isfile(ca_cert):
# Generate "CA"
tmp = f'openssl req -new -x509 -key {ssl_key} -out {ca_cert} -subj {subject}'
- out = cmd(tmp)
- print(out)
+ print(cmd(tmp))
+ if not os.path.isfile(dh_pem):
# Generate "DH" key
tmp = f'openssl dhparam -out {dh_pem} 2048'
- out = cmd(tmp)
- print(out)
+ print(cmd(tmp))
+ if not os.path.isfile(s2s_key):
# Generate site-2-site key
tmp = f'openvpn --genkey --secret {s2s_key}'
- out = cmd(tmp)
- print(out)
+ print(cmd(tmp))
+
+ if not os.path.isfile(auth_key):
+ # Generate TLS auth key
+ tmp = f'openvpn --genkey --secret {auth_key}'
+ print(cmd(tmp))
- for file in [ca_cert, ssl_cert, ssl_key, dh_pem, s2s_key]:
- cmd(f'sudo chown openvpn:openvpn {file}')
+ for file in [ca_cert, ssl_cert, ssl_key, dh_pem, s2s_key, auth_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 b5bde743b..43392bde3 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -22,7 +22,7 @@ import unittest
from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
base_path = ['nat']
src_path = base_path + ['source']
@@ -73,10 +73,10 @@ class TestNAT(unittest.TestCase):
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])
+ iface = dict_search('match.right', data['expr'][0])
+ direction = dict_search('match.left.payload.field', data['expr'][1])
+ address = dict_search('match.right.prefix.addr', data['expr'][1])
+ mask = dict_search('match.right.prefix.len', data['expr'][1])
if int(rule) < 200:
self.assertEqual(direction, 'saddr')
@@ -127,11 +127,11 @@ class TestNAT(unittest.TestCase):
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])
+ iface = dict_search('match.right', data['expr'][0])
+ direction = dict_search('match.left.payload.field', data['expr'][1])
+ protocol = dict_search('match.left.payload.protocol', data['expr'][1])
+ dnat_addr = dict_search('dnat.addr', data['expr'][3])
+ dnat_port = dict_search('dnat.port', data['expr'][3])
self.assertEqual(direction, 'sport')
self.assertEqual(dnat_addr, '192.0.2.1')
diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py
index 0cd00ccce..ea70d8e03 100755
--- a/smoketest/scripts/cli/test_service_ssh.py
+++ b/smoketest/scripts/cli/test_service_ssh.py
@@ -20,12 +20,14 @@ import unittest
from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
-from vyos.util import read_file
+from vyos.util import cmd
from vyos.util import process_named_running
+from vyos.util import read_file
PROCESS_NAME = 'sshd'
SSHD_CONF = '/run/ssh/sshd_config'
base_path = ['service', 'ssh']
+vrf = 'ssh-test'
def get_config_value(key):
tmp = read_file(SSHD_CONF)
@@ -44,6 +46,8 @@ class TestServiceSSH(unittest.TestCase):
self.session.delete(base_path)
# restore "plain" SSH access
self.session.set(base_path)
+ # delete VRF
+ self.session.delete(['vrf', 'name', vrf])
self.session.commit()
del self.session
@@ -129,5 +133,31 @@ class TestServiceSSH(unittest.TestCase):
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
+ def test_ssh_vrf(self):
+ """ Check if SSH service can be bound to given VRF """
+ port = '22'
+ self.session.set(base_path + ['port', port])
+ self.session.set(base_path + ['vrf', vrf])
+
+ # VRF does yet not exist - an error must be thrown
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+
+ self.session.set(['vrf', 'name', vrf, 'table', '1001'])
+
+ # commit changes
+ self.session.commit()
+
+ # Check configured port
+ tmp = get_config_value('Port')
+ self.assertIn(port, tmp)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ # Check for process in VRF
+ tmp = cmd(f'ip vrf pids {vrf}')
+ self.assertIn(PROCESS_NAME, tmp)
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 2187b3c73..d0c2dd252 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -23,7 +23,7 @@ from vyos.configdict import dict_merge
from vyos.hostsd_client import Client as hostsd_client
from vyos.util import call
from vyos.util import chown
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
from vyos.template import render
from vyos.xml import defaults
from vyos.validate import is_ipv6
@@ -94,7 +94,7 @@ def verify(dns):
if 'allow_from' not in dns:
raise ConfigError('DNS forwarding requires an allow-from network')
- # we can not use vyos_dict_search() when testing for domain servers
+ # we can not use dict_search() when testing for domain servers
# as a domain will contains dot's which is out dictionary delimiter.
if 'domain' in dns:
for domain in dns['domain']:
diff --git a/src/conf_mode/intel_qat.py b/src/conf_mode/intel_qat.py
index ab98cbc03..dd04a002d 100755
--- a/src/conf_mode/intel_qat.py
+++ b/src/conf_mode/intel_qat.py
@@ -66,8 +66,14 @@ def verify(qat):
# Check if QAT device exist
output, err = popen('lspci -nn', decode='utf-8')
if not err:
+ # PCI id | Chipset
+ # 19e2 -> C3xx
+ # 37c8 -> C62x
+ # 0435 -> DH895
+ # 6f54 -> D15xx
+ # 18ee -> QAT_200XX
data = re.findall(
- '(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)|(8086:1f18)', output)
+ '(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)|(8086:18ee)', 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 ea9bd54d4..1a549f27d 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -33,7 +33,7 @@ from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
from vyos.ifconfig import BondIf
from vyos.ifconfig import Section
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
from vyos.validate import has_address_configured
from vyos import ConfigError
from vyos import airbag
@@ -101,7 +101,7 @@ def get_config(config=None):
# also present the interfaces to be removed from the bond as dictionary
bond['member'].update({'interface_remove': tmp})
- if vyos_dict_search('member.interface', bond):
+ if dict_search('member.interface', bond):
for interface, interface_config in bond['member']['interface'].items():
# Check if member interface is already member of another bridge
tmp = is_member(conf, interface, 'bridge')
@@ -151,7 +151,7 @@ def verify(bond):
verify_vlan_config(bond)
bond_name = bond['ifname']
- if vyos_dict_search('member.interface', bond):
+ if dict_search('member.interface', bond):
for interface, interface_config in bond['member']['interface'].items():
error_msg = f'Can not add interface "{interface}" to bond, '
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index 4aeb8fc67..258f9ec79 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -32,7 +32,7 @@ from vyos.validate import has_address_configured
from vyos.xml import defaults
from vyos.util import cmd
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
from vyos import ConfigError
from vyos import airbag
@@ -58,7 +58,7 @@ def get_config(config=None):
else:
bridge.update({'member': {'interface_remove': tmp }})
- if vyos_dict_search('member.interface', bridge):
+ if dict_search('member.interface', bridge):
# XXX: T2665: we need a copy of the dict keys for iteration, else we will get:
# RuntimeError: dictionary changed size during iteration
for interface in list(bridge['member']['interface']):
@@ -100,7 +100,7 @@ def verify(bridge):
verify_dhcpv6(bridge)
verify_vrf(bridge)
- if vyos_dict_search('member.interface', bridge):
+ if dict_search('member.interface', bridge):
for interface, interface_config in bridge['member']['interface'].items():
error_msg = f'Can not add interface "{interface}" to bridge, '
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 3f4965029..b1318b9ee 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -17,110 +17,38 @@
import os
import re
-from copy import deepcopy
-from sys import exit,stderr
-from ipaddress import ip_address,ip_network,IPv4Address,IPv4Network,IPv6Address,IPv6Network,summarize_address_range
+from sys import exit
+from ipaddress import IPv4Address
+from ipaddress import IPv4Network
+from ipaddress import IPv6Address
+from ipaddress import IPv6Network
+from ipaddress import summarize_address_range
from netifaces import interfaces
from shutil import rmtree
from vyos.config import Config
-from vyos.configdict import list_diff
-from vyos.configdict import is_member
+from vyos.configdict import get_interface_dict
+from vyos.configverify import verify_vrf
+from vyos.configverify import verify_bridge_delete
+from vyos.configverify import verify_diffie_hellman_length
from vyos.ifconfig import VTunIf
from vyos.template import render
-from vyos.util import call, chown, chmod_600, chmod_755
-from vyos.validate import is_addr_assigned, is_ipv4
-from vyos import ConfigError
-from vyos.util import cmd
+from vyos.util import call
+from vyos.util import chown
+from vyos.util import chmod_600
+from vyos.util import dict_search
+from vyos.validate import is_addr_assigned
+from vyos.validate import is_ipv4
+from vyos.validate import is_ipv6
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
user = 'openvpn'
group = 'openvpn'
-default_config_data = {
- 'address': [],
- 'auth_user': '',
- 'auth_pass': '',
- 'auth_user_pass_file': '',
- 'auth': False,
- 'compress_lzo': False,
- 'deleted': False,
- 'description': '',
- 'disable': False,
- 'disable_ncp': False,
- 'encryption': '',
- 'hash': '',
- 'intf': '',
- 'ipv6_accept_ra': 1,
- 'ipv6_autoconf': 0,
- 'ipv6_eui64_prefix': [],
- 'ipv6_eui64_prefix_remove': [],
- 'ipv6_forwarding': 1,
- 'ipv6_dup_addr_detect': 1,
- 'ipv6_local_address': [],
- 'ipv6_remote_address': [],
- 'is_bridge_member': False,
- 'ping_restart': '60',
- 'ping_interval': '10',
- 'local_address': [],
- 'local_address_subnet': '',
- 'local_host': '',
- 'local_port': '',
- 'mode': '',
- 'ncp_ciphers': '',
- 'options': [],
- 'persistent_tunnel': False,
- 'protocol': 'udp',
- 'protocol_real': '',
- 'redirect_gateway': '',
- 'remote_address': [],
- 'remote_host': [],
- 'remote_port': '',
- 'client': [],
- 'server_domain': '',
- 'server_max_conn': '',
- 'server_dns_nameserver': [],
- 'server_pool': True,
- 'server_pool_start': '',
- 'server_pool_stop': '',
- 'server_pool_netmask': '',
- 'server_push_route': [],
- 'server_reject_unconfigured': False,
- 'server_subnet': [],
- 'server_topology': '',
- 'server_ipv6_dns_nameserver': [],
- 'server_ipv6_local': '',
- 'server_ipv6_prefixlen': '',
- 'server_ipv6_remote': '',
- 'server_ipv6_pool': True,
- 'server_ipv6_pool_base': '',
- 'server_ipv6_pool_prefixlen': '',
- 'server_ipv6_push_route': [],
- 'server_ipv6_subnet': [],
- 'shared_secret_file': '',
- 'tls': False,
- 'tls_auth': '',
- 'tls_ca_cert': '',
- 'tls_cert': '',
- 'tls_crl': '',
- 'tls_dh': '',
- 'tls_key': '',
- 'tls_crypt': '',
- 'tls_role': '',
- 'tls_version_min': '',
- 'type': 'tun',
- 'uid': user,
- 'gid': group,
- 'vrf': ''
-}
-
-
-def get_config_name(intf):
- cfg_file = f'/run/openvpn/{intf}.conf'
- return cfg_file
-
+cfg_file = '/run/openvpn/{ifname}.conf'
def checkCertHeader(header, filename):
"""
@@ -137,600 +65,125 @@ def checkCertHeader(header, filename):
return False
-def getDefaultServer(network, topology, devtype):
+def get_config(config=None):
"""
- Gets the default server parameters for a IPv4 "server" directive.
- Logic from openvpn's src/openvpn/helper.c.
- Returns a dict with addresses or False if the input parameters were incorrect.
+ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
+ interface name will be added or a deleted flag
"""
- if not (devtype == 'tun' or devtype == 'tap'):
- return False
-
- if not network.version == 4:
- return False
- elif (devtype == 'tun' and network.prefixlen > 29) or (devtype == 'tap' and network.prefixlen > 30):
- return False
-
- server = {
- 'local': '',
- 'remote_netmask': '',
- 'client_remote_netmask': '',
- 'pool_start': '',
- 'pool_stop': '',
- 'pool_netmask': ''
- }
-
- if devtype == 'tun':
- if topology == 'net30' or topology == 'point-to-point':
- server['local'] = network[1]
- server['remote_netmask'] = network[2]
- server['client_remote_netmask'] = server['local']
-
- # pool start is 4th host IP in subnet (.4 in a /24)
- server['pool_start'] = network[4]
-
- if network.prefixlen == 29:
- server['pool_stop'] = network.broadcast_address
- else:
- # pool end is -4 from the broadcast address (.251 in a /24)
- server['pool_stop'] = network[-5]
-
- elif topology == 'subnet':
- server['local'] = network[1]
- server['remote_netmask'] = str(network.netmask)
- server['client_remote_netmask'] = server['remote_netmask']
- server['pool_start'] = network[2]
- server['pool_stop'] = network[-3]
- server['pool_netmask'] = server['remote_netmask']
-
- elif devtype == 'tap':
- server['local'] = network[1]
- server['remote_netmask'] = str(network.netmask)
- server['client_remote_netmask'] = server['remote_netmask']
- server['pool_start'] = network[2]
- server['pool_stop'] = network[-2]
- server['pool_netmask'] = server['remote_netmask']
-
- return server
-
-def get_config(config=None):
- openvpn = deepcopy(default_config_data)
if config:
conf = config
else:
conf = Config()
+ base = ['interfaces', 'openvpn']
+ openvpn = get_interface_dict(conf, base)
- # determine tagNode instance
- if 'VYOS_TAGNODE_VALUE' not in os.environ:
- raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified')
-
- openvpn['intf'] = os.environ['VYOS_TAGNODE_VALUE']
- openvpn['auth_user_pass_file'] = f"/run/openvpn/{openvpn['intf']}.pw"
-
- # check if interface is member of a 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']):
- openvpn['deleted'] = True
- return openvpn
-
- # bridged server should not have a pool by default (but can be specified manually)
- if openvpn['is_bridge_member']:
- openvpn['server_pool'] = False
- openvpn['server_ipv6_pool'] = False
-
- # set configuration level
- conf.set_level('interfaces openvpn ' + openvpn['intf'])
-
- # retrieve authentication options - username
- if conf.exists('authentication username'):
- openvpn['auth_user'] = conf.return_value('authentication username')
- openvpn['auth'] = True
-
- # retrieve authentication options - username
- if conf.exists('authentication password'):
- openvpn['auth_pass'] = conf.return_value('authentication password')
- openvpn['auth'] = True
-
- # retrieve interface description
- if conf.exists('description'):
- openvpn['description'] = conf.return_value('description')
-
- # interface device-type
- if conf.exists('device-type'):
- openvpn['type'] = conf.return_value('device-type')
-
- # disable interface
- if conf.exists('disable'):
- openvpn['disable'] = True
-
- # data encryption algorithm cipher
- if conf.exists('encryption cipher'):
- openvpn['encryption'] = conf.return_value('encryption cipher')
-
- # disable ncp-ciphers support
- if conf.exists('encryption disable-ncp'):
- openvpn['disable_ncp'] = True
-
- # data encryption algorithm ncp-list
- if conf.exists('encryption ncp-ciphers'):
- _ncp_ciphers = []
- for enc in conf.return_values('encryption ncp-ciphers'):
- if enc == 'none':
- _ncp_ciphers.append('none')
- _ncp_ciphers.append('NONE')
- elif enc == 'des':
- _ncp_ciphers.append('des-cbc')
- _ncp_ciphers.append('DES-CBC')
- elif enc == '3des':
- _ncp_ciphers.append('des-ede3-cbc')
- _ncp_ciphers.append('DES-EDE3-CBC')
- elif enc == 'aes128':
- _ncp_ciphers.append('aes-128-cbc')
- _ncp_ciphers.append('AES-128-CBC')
- elif enc == 'aes128gcm':
- _ncp_ciphers.append('aes-128-gcm')
- _ncp_ciphers.append('AES-128-GCM')
- elif enc == 'aes192':
- _ncp_ciphers.append('aes-192-cbc')
- _ncp_ciphers.append('AES-192-CBC')
- elif enc == 'aes192gcm':
- _ncp_ciphers.append('aes-192-gcm')
- _ncp_ciphers.append('AES-192-GCM')
- elif enc == 'aes256':
- _ncp_ciphers.append('aes-256-cbc')
- _ncp_ciphers.append('AES-256-CBC')
- elif enc == 'aes256gcm':
- _ncp_ciphers.append('aes-256-gcm')
- _ncp_ciphers.append('AES-256-GCM')
- openvpn['ncp_ciphers'] = ':'.join(_ncp_ciphers)
-
- # hash algorithm
- if conf.exists('hash'):
- openvpn['hash'] = conf.return_value('hash')
-
- # Maximum number of keepalive packet failures
- if conf.exists('keep-alive failure-count') and conf.exists('keep-alive interval'):
- fail_count = conf.return_value('keep-alive failure-count')
- interval = conf.return_value('keep-alive interval')
- openvpn['ping_interval' ] = interval
- openvpn['ping_restart' ] = int(interval) * int(fail_count)
-
- # Local IP address of tunnel - even as it is a tag node - we can only work
- # on the first address
- if conf.exists('local-address'):
- for tmp in conf.list_nodes('local-address'):
- tmp_ip = ip_address(tmp)
- if tmp_ip.version == 4:
- openvpn['local_address'].append(tmp)
- if conf.exists('local-address {} subnet-mask'.format(tmp)):
- openvpn['local_address_subnet'] = conf.return_value('local-address {} subnet-mask'.format(tmp))
- elif tmp_ip.version == 6:
- # input IPv6 address could be expanded so get the compressed version
- openvpn['ipv6_local_address'].append(str(tmp_ip))
-
- # Local IP address to accept connections
- if conf.exists('local-host'):
- openvpn['local_host'] = conf.return_value('local-host')
-
- # Local port number to accept connections
- if conf.exists('local-port'):
- openvpn['local_port'] = conf.return_value('local-port')
-
- # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC)
- if conf.exists('ipv6 address autoconf'):
- openvpn['ipv6_autoconf'] = 1
-
- # Get prefixes for IPv6 addressing based on MAC address (EUI-64)
- if conf.exists('ipv6 address eui64'):
- openvpn['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64')
-
- # Determine currently effective EUI64 addresses - to determine which
- # address is no longer valid and needs to be removed
- eff_addr = conf.return_effective_values('ipv6 address eui64')
- openvpn['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, openvpn['ipv6_eui64_prefix'])
-
- # Remove the default link-local address if set.
- if conf.exists('ipv6 address no-default-link-local'):
- openvpn['ipv6_eui64_prefix_remove'].append('fe80::/64')
- else:
- # add the link-local by default to make IPv6 work
- openvpn['ipv6_eui64_prefix'].append('fe80::/64')
-
- # Disable IPv6 forwarding on this interface
- if conf.exists('ipv6 disable-forwarding'):
- openvpn['ipv6_forwarding'] = 0
-
- # IPv6 Duplicate Address Detection (DAD) tries
- if conf.exists('ipv6 dup-addr-detect-transmits'):
- openvpn['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits'))
-
- # to make IPv6 SLAAC and DHCPv6 work with forwarding=1,
- # accept_ra must be 2
- if openvpn['ipv6_autoconf'] or 'dhcpv6' in openvpn['address']:
- openvpn['ipv6_accept_ra'] = 2
-
- # OpenVPN operation mode
- if conf.exists('mode'):
- openvpn['mode'] = conf.return_value('mode')
-
- # Additional OpenVPN options
- if conf.exists('openvpn-option'):
- openvpn['options'] = conf.return_values('openvpn-option')
-
- # Do not close and reopen interface
- if conf.exists('persistent-tunnel'):
- openvpn['persistent_tunnel'] = True
-
- # Communication protocol
- if conf.exists('protocol'):
- openvpn['protocol'] = conf.return_value('protocol')
-
- # IP address of remote end of tunnel
- if conf.exists('remote-address'):
- for tmp in conf.return_values('remote-address'):
- tmp_ip = ip_address(tmp)
- if tmp_ip.version == 4:
- openvpn['remote_address'].append(tmp)
- elif tmp_ip.version == 6:
- openvpn['ipv6_remote_address'].append(str(tmp_ip))
-
- # Remote host to connect to (dynamic if not set)
- if conf.exists('remote-host'):
- openvpn['remote_host'] = conf.return_values('remote-host')
-
- # Remote port number to connect to
- if conf.exists('remote-port'):
- openvpn['remote_port'] = conf.return_value('remote-port')
-
- # OpenVPN tunnel to be used as the default route
- # see https://openvpn.net/community-resources/reference-manual-for-openvpn-2-4/
- # redirect-gateway flags
- if conf.exists('replace-default-route'):
- openvpn['redirect_gateway'] = 'def1'
-
- if conf.exists('replace-default-route local'):
- openvpn['redirect_gateway'] = 'local def1'
-
- # Topology for clients
- if conf.exists('server topology'):
- openvpn['server_topology'] = conf.return_value('server topology')
-
- # Server-mode subnet (from which client IPs are allocated)
- server_network_v4 = None
- server_network_v6 = None
- if conf.exists('server subnet'):
- for tmp in conf.return_values('server subnet'):
- tmp_ip = ip_network(tmp)
- if tmp_ip.version == 4:
- server_network_v4 = tmp_ip
- # convert the network to format: "192.0.2.0 255.255.255.0" for later use in template
- openvpn['server_subnet'].append(tmp_ip.with_netmask.replace(r'/', ' '))
- elif tmp_ip.version == 6:
- server_network_v6 = tmp_ip
- openvpn['server_ipv6_subnet'].append(str(tmp_ip))
-
- # Client-specific settings
- for client in conf.list_nodes('server client'):
- # set configuration level
- conf.set_level('interfaces openvpn ' + openvpn['intf'] + ' server client ' + client)
- data = {
- 'name': client,
- 'disable': False,
- 'ip': [],
- 'ipv6_ip': [],
- 'ipv6_remote': '',
- 'ipv6_push_route': [],
- 'ipv6_subnet': [],
- 'push_route': [],
- 'subnet': [],
- 'remote_netmask': ''
- }
-
- # Option to disable client connection
- if conf.exists('disable'):
- data['disable'] = True
-
- # IP address of the client
- for tmp in conf.return_values('ip'):
- tmp_ip = ip_address(tmp)
- if tmp_ip.version == 4:
- data['ip'].append(tmp)
- elif tmp_ip.version == 6:
- data['ipv6_ip'].append(str(tmp_ip))
-
- # Route to be pushed to the client
- for tmp in conf.return_values('push-route'):
- tmp_ip = ip_network(tmp)
- if tmp_ip.version == 4:
- data['push_route'].append(tmp_ip.with_netmask.replace(r'/', ' '))
- elif tmp_ip.version == 6:
- data['ipv6_push_route'].append(str(tmp_ip))
-
- # Subnet belonging to the client
- for tmp in conf.return_values('subnet'):
- tmp_ip = ip_network(tmp)
- if tmp_ip.version == 4:
- data['subnet'].append(tmp_ip.with_netmask.replace(r'/', ' '))
- elif tmp_ip.version == 6:
- data['ipv6_subnet'].append(str(tmp_ip))
-
- # Append to global client list
- openvpn['client'].append(data)
-
- # re-set configuration level
- conf.set_level('interfaces openvpn ' + openvpn['intf'])
-
- # Server client IP pool
- if conf.exists('server client-ip-pool'):
- conf.set_level('interfaces openvpn ' + openvpn['intf'] + ' server client-ip-pool')
-
- # enable or disable server_pool where necessary
- # default is enabled, or disabled in bridge mode
- openvpn['server_pool'] = not conf.exists('disable')
-
- if conf.exists('start'):
- openvpn['server_pool_start'] = conf.return_value('start')
-
- if conf.exists('stop'):
- openvpn['server_pool_stop'] = conf.return_value('stop')
-
- if conf.exists('netmask'):
- openvpn['server_pool_netmask'] = conf.return_value('netmask')
-
- conf.set_level('interfaces openvpn ' + openvpn['intf'])
-
- # Server client IPv6 pool
- if conf.exists('server client-ipv6-pool'):
- conf.set_level('interfaces openvpn ' + openvpn['intf'] + ' server client-ipv6-pool')
- openvpn['server_ipv6_pool'] = not conf.exists('disable')
- if conf.exists('base'):
- tmp = conf.return_value('base').split('/')
- openvpn['server_ipv6_pool_base'] = str(IPv6Address(tmp[0]))
- if 1 < len(tmp):
- openvpn['server_ipv6_pool_prefixlen'] = tmp[1]
-
- conf.set_level('interfaces openvpn ' + openvpn['intf'])
-
- # DNS suffix to be pushed to all clients
- if conf.exists('server domain-name'):
- openvpn['server_domain'] = conf.return_value('server domain-name')
-
- # Number of maximum client connections
- if conf.exists('server max-connections'):
- openvpn['server_max_conn'] = conf.return_value('server max-connections')
-
- # Domain Name Server (DNS)
- if conf.exists('server name-server'):
- for tmp in conf.return_values('server name-server'):
- tmp_ip = ip_address(tmp)
- if tmp_ip.version == 4:
- openvpn['server_dns_nameserver'].append(tmp)
- elif tmp_ip.version == 6:
- openvpn['server_ipv6_dns_nameserver'].append(str(tmp_ip))
-
- # Route to be pushed to all clients
- if conf.exists('server push-route'):
- for tmp in conf.return_values('server push-route'):
- tmp_ip = ip_network(tmp)
- if tmp_ip.version == 4:
- openvpn['server_push_route'].append(tmp_ip.with_netmask.replace(r'/', ' '))
- elif tmp_ip.version == 6:
- openvpn['server_ipv6_push_route'].append(str(tmp_ip))
-
- # Reject connections from clients that are not explicitly configured
- if conf.exists('server reject-unconfigured-clients'):
- openvpn['server_reject_unconfigured'] = True
-
- # File containing TLS auth static key
- if conf.exists('tls auth-file'):
- openvpn['tls_auth'] = conf.return_value('tls auth-file')
- openvpn['tls'] = True
-
- # File containing certificate for Certificate Authority (CA)
- if conf.exists('tls ca-cert-file'):
- openvpn['tls_ca_cert'] = conf.return_value('tls ca-cert-file')
- openvpn['tls'] = True
-
- # File containing certificate for this host
- if conf.exists('tls cert-file'):
- openvpn['tls_cert'] = conf.return_value('tls cert-file')
- openvpn['tls'] = True
-
- # File containing certificate revocation list (CRL) for this host
- if conf.exists('tls crl-file'):
- openvpn['tls_crl'] = conf.return_value('tls crl-file')
- openvpn['tls'] = True
-
- # File containing Diffie Hellman parameters (server only)
- if conf.exists('tls dh-file'):
- openvpn['tls_dh'] = conf.return_value('tls dh-file')
- openvpn['tls'] = True
-
- # File containing this host's private key
- if conf.exists('tls key-file'):
- openvpn['tls_key'] = conf.return_value('tls key-file')
- openvpn['tls'] = True
-
- # File containing key to encrypt control channel packets
- if conf.exists('tls crypt-file'):
- openvpn['tls_crypt'] = conf.return_value('tls crypt-file')
- openvpn['tls'] = True
-
- # Role in TLS negotiation
- if conf.exists('tls role'):
- openvpn['tls_role'] = conf.return_value('tls role')
- openvpn['tls'] = True
-
- # Minimum required TLS version
- if conf.exists('tls tls-version-min'):
- openvpn['tls_version_min'] = conf.return_value('tls tls-version-min')
- openvpn['tls'] = True
-
- if conf.exists('shared-secret-key-file'):
- openvpn['shared_secret_file'] = conf.return_value('shared-secret-key-file')
-
- if conf.exists('use-lzo-compression'):
- openvpn['compress_lzo'] = True
-
- # Special case when using EC certificates:
- # if key-file is EC and dh-file is unset, set tls_dh to 'none'
- if not openvpn['tls_dh'] and openvpn['tls_key'] and checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls_key']):
- openvpn['tls_dh'] = 'none'
-
- # set default server topology to net30
- if openvpn['mode'] == 'server' and not openvpn['server_topology']:
- openvpn['server_topology'] = 'net30'
-
- # Convert protocol to real protocol used by openvpn.
- # To make openvpn listen on both IPv4 and IPv6 we must use *6 protocols
- # (https://community.openvpn.net/openvpn/ticket/360), unless the local-host
- # or each of the remote-host in client mode is IPv4
- # in which case it must use the standard protocols.
- if openvpn['protocol'] == 'tcp-active':
- openvpn['protocol_real'] = 'tcp6-client'
- elif openvpn['protocol'] == 'tcp-passive':
- openvpn['protocol_real'] = 'tcp6-server'
- else:
- openvpn['protocol_real'] = 'udp6'
-
- if ( is_ipv4(openvpn['local_host']) or
- # in client mode test all the remotes instead
- (openvpn['mode'] == 'client' and all([is_ipv4(h) for h in openvpn['remote_host']])) ):
- # takes out the '6'
- openvpn['protocol_real'] = openvpn['protocol_real'][:3] + openvpn['protocol_real'][4:]
-
- # Set defaults where necessary.
- # If any of the input parameters are wrong,
- # this will return False and no defaults will be set.
- if server_network_v4 and openvpn['server_topology'] and openvpn['type']:
- default_server = None
- default_server = getDefaultServer(server_network_v4, openvpn['server_topology'], openvpn['type'])
- if default_server:
- # server-bridge doesn't require a pool so don't set defaults for it
- if openvpn['server_pool'] and not openvpn['is_bridge_member']:
- if not openvpn['server_pool_start']:
- openvpn['server_pool_start'] = default_server['pool_start']
-
- if not openvpn['server_pool_stop']:
- openvpn['server_pool_stop'] = default_server['pool_stop']
-
- if not openvpn['server_pool_netmask']:
- openvpn['server_pool_netmask'] = default_server['pool_netmask']
-
- for client in openvpn['client']:
- client['remote_netmask'] = default_server['client_remote_netmask']
-
- if server_network_v6:
- if not openvpn['server_ipv6_local']:
- openvpn['server_ipv6_local'] = server_network_v6[1]
- if not openvpn['server_ipv6_prefixlen']:
- openvpn['server_ipv6_prefixlen'] = server_network_v6.prefixlen
- if not openvpn['server_ipv6_remote']:
- openvpn['server_ipv6_remote'] = server_network_v6[2]
-
- if openvpn['server_ipv6_pool'] and server_network_v6.prefixlen < 112:
- if not openvpn['server_ipv6_pool_base']:
- openvpn['server_ipv6_pool_base'] = server_network_v6[0x1000]
- if not openvpn['server_ipv6_pool_prefixlen']:
- openvpn['server_ipv6_pool_prefixlen'] = openvpn['server_ipv6_prefixlen']
-
- for client in openvpn['client']:
- client['ipv6_remote'] = openvpn['server_ipv6_local']
-
- if openvpn['redirect_gateway']:
- openvpn['redirect_gateway'] += ' ipv6'
-
- # retrieve VRF instance
- if conf.exists('vrf'):
- openvpn['vrf'] = conf.return_value('vrf')
+ openvpn['auth_user_pass_file'] = '/run/openvpn/{ifname}.pw'.format(**openvpn)
+ openvpn['daemon_user'] = user
+ openvpn['daemon_group'] = group
return openvpn
def verify(openvpn):
- if openvpn['deleted']:
- if openvpn['is_bridge_member']:
- raise ConfigError((
- f'Cannot delete interface "{openvpn["intf"]}" as it is a '
- f'member of bridge "{openvpn["is_bridge_menber"]}"!'))
+ if 'deleted' in openvpn:
+ verify_bridge_delete(openvpn)
return None
-
- if not openvpn['mode']:
- raise ConfigError('Must specify OpenVPN operation mode')
+ if 'mode' not in openvpn:
+ raise ConfigError('Must specify OpenVPN operation mode!')
# Check if we have disabled ncp and at the same time specified ncp-ciphers
- if openvpn['disable_ncp'] and openvpn['ncp_ciphers']:
- raise ConfigError('Cannot specify both "encryption disable-ncp" and "encryption ncp-ciphers"')
+ if 'encryption' in openvpn:
+ if {'disable_ncp', 'ncp_ciphers'} <= set(openvpn.get('encryption')):
+ raise ConfigError('Can not specify both "encryption disable-ncp" '\
+ 'and "encryption ncp-ciphers"')
+
#
# OpenVPN client mode - VERIFY
#
if openvpn['mode'] == 'client':
- if openvpn['local_port']:
+ if 'local_port' in openvpn:
raise ConfigError('Cannot specify "local-port" in client mode')
- if openvpn['local_host']:
+ if 'local_host' in openvpn:
raise ConfigError('Cannot specify "local-host" in client mode')
+ if 'remote_host' not in openvpn:
+ raise ConfigError('Must specify "remote-host" in client mode')
+
if openvpn['protocol'] == 'tcp-passive':
raise ConfigError('Protocol "tcp-passive" is not valid in client mode')
- if not openvpn['remote_host']:
- raise ConfigError('Must specify "remote-host" in client mode')
-
- if openvpn['tls_dh'] and openvpn['tls_dh'] != 'none':
+ if dict_search('tls.dh_file', openvpn):
raise ConfigError('Cannot specify "tls dh-file" in client mode')
#
# OpenVPN site-to-site - VERIFY
#
- if openvpn['mode'] == 'site-to-site':
- if openvpn['ncp_ciphers']:
- raise ConfigError('encryption ncp-ciphers cannot be specified in site-to-site mode, only server or client')
-
- if openvpn['mode'] == 'site-to-site' and not openvpn['is_bridge_member']:
- if not (openvpn['local_address'] or openvpn['ipv6_local_address']):
+ elif openvpn['mode'] == 'site-to-site':
+ if not 'local_address' in openvpn:
raise ConfigError('Must specify "local-address" or add interface to bridge')
- if len(openvpn['local_address']) > 1 or len(openvpn['ipv6_local_address']) > 1:
- raise ConfigError('Cannot specify more than 1 IPv4 and 1 IPv6 "local-address"')
+ if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1:
+ raise ConfigError('Only one IPv4 local-address can be specified')
- if len(openvpn['remote_address']) > 1 or len(openvpn['ipv6_remote_address']) > 1:
- raise ConfigError('Cannot specify more than 1 IPv4 and 1 IPv6 "remote-address"')
+ if len([addr for addr in openvpn['local_address'] if is_ipv6(addr)]) > 1:
+ raise ConfigError('Only one IPv6 local-address can be specified')
- for host in openvpn['remote_host']:
- if host in openvpn['remote_address'] or host in openvpn['ipv6_remote_address']:
- raise ConfigError('"remote-address" cannot be the same as "remote-host"')
+ if openvpn['device_type'] == 'tun':
+ if 'remote_address' not in openvpn:
+ raise ConfigError('Must specify "remote-address"')
- if openvpn['local_address'] and not (openvpn['remote_address'] or openvpn['local_address_subnet']):
- raise ConfigError('IPv4 "local-address" requires IPv4 "remote-address" or IPv4 "local-address subnet"')
+ if 'remote_address' in openvpn:
+ if len([addr for addr in openvpn['remote_address'] if is_ipv4(addr)]) > 1:
+ raise ConfigError('Only one IPv4 remote-address can be specified')
- if openvpn['remote_address'] and not openvpn['local_address']:
- raise ConfigError('IPv4 "remote-address" requires IPv4 "local-address"')
+ if len([addr for addr in openvpn['remote_address'] if is_ipv6(addr)]) > 1:
+ raise ConfigError('Only one IPv6 remote-address can be specified')
- if openvpn['ipv6_local_address'] and not openvpn['ipv6_remote_address']:
- raise ConfigError('IPv6 "local-address" requires IPv6 "remote-address"')
+ if not 'local_address' in openvpn:
+ raise ConfigError('"remote-address" requires "local-address"')
- if openvpn['ipv6_remote_address'] and not openvpn['ipv6_local_address']:
- raise ConfigError('IPv6 "remote-address" requires IPv6 "local-address"')
+ v4loAddr = [addr for addr in openvpn['local_address'] if is_ipv4(addr)]
+ v4remAddr = [addr for addr in openvpn['remote_address'] if is_ipv4(addr)]
+ if v4loAddr and not v4remAddr:
+ raise ConfigError('IPv4 "local-address" requires IPv4 "remote-address"')
+ elif v4remAddr and not v4loAddr:
+ raise ConfigError('IPv4 "remote-address" requires IPv4 "local-address"')
- if openvpn['type'] == 'tun':
- if not (openvpn['remote_address'] or openvpn['ipv6_remote_address']):
- raise ConfigError('Must specify "remote-address"')
+ v6remAddr = [addr for addr in openvpn['remote_address'] if is_ipv6(addr)]
+ v6loAddr = [addr for addr in openvpn['local_address'] if is_ipv6(addr)]
+ if v6loAddr and not v6remAddr:
+ raise ConfigError('IPv6 "local-address" requires IPv6 "remote-address"')
+ elif v6remAddr and not v6loAddr:
+ raise ConfigError('IPv6 "remote-address" requires IPv6 "local-address"')
- if ( (openvpn['local_address'] and openvpn['local_address'] == openvpn['remote_address']) or
- (openvpn['ipv6_local_address'] and openvpn['ipv6_local_address'] == openvpn['ipv6_remote_address']) ):
+ if (v4loAddr == v4remAddr) or (v6remAddr == v4remAddr):
raise ConfigError('"local-address" and "remote-address" cannot be the same')
- if openvpn['local_host'] in openvpn['local_address'] or openvpn['local_host'] in openvpn['ipv6_local_address']:
+ if dict_search('local_host', openvpn) in dict_search('local_address', openvpn):
raise ConfigError('"local-address" cannot be the same as "local-host"')
+ if dict_search('remote_host', openvpn) in dict_search('remote_address', openvpn):
+ raise ConfigError('"remote-address" and "remote-host" can not be the same')
+
+
+ if 'local_address' in openvpn:
+ # we can only have one local_address, this is ensured above
+ v4addr = None
+ for laddr in openvpn['local_address']:
+ if is_ipv4(laddr): v4addr = laddr
+
+ if 'remote_address' not in openvpn and (v4addr not in openvpn['local_address'] or 'subnet_mask' not in openvpn['local_address'][v4addr]):
+ raise ConfigError('IPv4 "local-address" requires IPv4 "remote-address" or IPv4 "local-address subnet"')
+
+ if dict_search('encryption.ncp_ciphers', openvpn):
+ raise ConfigError('NCP ciphers can only be used in client or server mode')
+
else:
# checks for client-server or site-to-site bridged
- if openvpn['local_address'] or openvpn['ipv6_local_address'] or openvpn['remote_address'] or openvpn['ipv6_remote_address']:
- raise ConfigError('Cannot specify "local-address" or "remote-address" in client-server or bridge mode')
+ if 'local_address' in openvpn or 'remote_address' in openvpn:
+ raise ConfigError('Cannot specify "local-address" or "remote-address" ' \
+ 'in client/server or bridge mode')
#
# OpenVPN server mode - VERIFY
@@ -739,47 +192,57 @@ def verify(openvpn):
if openvpn['protocol'] == 'tcp-active':
raise ConfigError('Protocol "tcp-active" is not valid in server mode')
- if openvpn['remote_port']:
+ if 'remote_port' in openvpn:
raise ConfigError('Cannot specify "remote-port" in server mode')
- if openvpn['remote_host']:
+ if 'remote_host' in openvpn:
raise ConfigError('Cannot specify "remote-host" in server mode')
- if openvpn['protocol'] == 'tcp-passive' and len(openvpn['remote_host']) > 1:
- raise ConfigError('Cannot specify more than 1 "remote-host" with "tcp-passive"')
-
- if not openvpn['tls_dh'] and not checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls_key']):
- raise ConfigError('Must specify "tls dh-file" when not using EC keys in server mode')
-
- if len(openvpn['server_subnet']) > 1 or len(openvpn['server_ipv6_subnet']) > 1:
- raise ConfigError('Cannot specify more than 1 IPv4 and 1 IPv6 server subnet')
-
- for client in openvpn['client']:
- if len(client['ip']) > 1 or len(client['ipv6_ip']) > 1:
- raise ConfigError(f'Server client "{client["name"]}": cannot specify more than 1 IPv4 and 1 IPv6 IP')
+ if 'tls' in openvpn:
+ if 'dh_file' not in openvpn['tls']:
+ if 'key_file' in openvpn['tls'] and not checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls']['key_file']):
+ raise ConfigError('Must specify "tls dh-file" when not using EC keys in server mode')
+
+ tmp = dict_search('server.subnet', openvpn)
+ if tmp:
+ v4_subnets = len([subnet for subnet in tmp if is_ipv4(subnet)])
+ v6_subnets = len([subnet for subnet in tmp if is_ipv6(subnet)])
+ if v4_subnets > 1:
+ raise ConfigError('Cannot specify more than 1 IPv4 server subnet')
+ if v6_subnets > 1:
+ raise ConfigError('Cannot specify more than 1 IPv6 server subnet')
+
+ if v6_subnets > 0 and v4_subnets == 0:
+ raise ConfigError('IPv6 server requires an IPv4 server subnet')
- if openvpn['server_subnet']:
- subnet = IPv4Network(openvpn['server_subnet'][0].replace(' ', '/'))
+ for subnet in tmp:
+ if is_ipv4(subnet):
+ subnet = IPv4Network(subnet)
- if openvpn['type'] == 'tun' and subnet.prefixlen > 29:
- raise ConfigError('Server subnets smaller than /29 with device type "tun" are not supported')
- elif openvpn['type'] == 'tap' and subnet.prefixlen > 30:
- raise ConfigError('Server subnets smaller than /30 with device type "tap" are not supported')
+ if openvpn['device_type'] == 'tun' and subnet.prefixlen > 29:
+ raise ConfigError('Server subnets smaller than /29 with device type "tun" are not supported')
+ elif openvpn['device_type'] == 'tap' and subnet.prefixlen > 30:
+ raise ConfigError('Server subnets smaller than /30 with device type "tap" are not supported')
- for client in openvpn['client']:
- if client['ip'] and not IPv4Address(client['ip'][0]) in subnet:
- raise ConfigError(f'Client "{client["name"]}" IP {client["ip"][0]} not in server subnet {subnet}')
+ for client in (dict_search('client', openvpn) or []):
+ if client['ip'] and not IPv4Address(client['ip'][0]) in subnet:
+ raise ConfigError(f'Client "{client["name"]}" IP {client["ip"][0]} not in server subnet {subnet}')
else:
- if not openvpn['is_bridge_member']:
+ if 'is_bridge_member' not in openvpn:
raise ConfigError('Must specify "server subnet" or add interface to bridge in server mode')
- if openvpn['server_pool']:
- if not (openvpn['server_pool_start'] and openvpn['server_pool_stop']):
- raise ConfigError('Server client-ip-pool requires both start and stop addresses in bridged mode')
+
+ for client in (dict_search('client', openvpn) or []):
+ if len(client['ip']) > 1 or len(client['ipv6_ip']) > 1:
+ raise ConfigError(f'Server client "{client["name"]}": cannot specify more than 1 IPv4 and 1 IPv6 IP')
+
+ if dict_search('server.client_ip_pool', openvpn):
+ if not (dict_search('server.client_ip_pool.start', openvpn) and dict_search('server.client_ip_pool.stop', openvpn)):
+ raise ConfigError('Server client-ip-pool requires both start and stop addresses')
else:
- v4PoolStart = IPv4Address(openvpn['server_pool_start'])
- v4PoolStop = IPv4Address(openvpn['server_pool_stop'])
+ v4PoolStart = IPv4Address(dict_search('server.client_ip_pool.start', openvpn))
+ v4PoolStop = IPv4Address(dict_search('server.client_ip_pool.stop', openvpn))
if v4PoolStart > v4PoolStop:
raise ConfigError(f'Server client-ip-pool start address {v4PoolStart} is larger than stop address {v4PoolStop}')
@@ -788,59 +251,57 @@ def verify(openvpn):
raise ConfigError(f'Server client-ip-pool is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.')
v4PoolNets = list(summarize_address_range(v4PoolStart, v4PoolStop))
- for client in openvpn['client']:
+ for client in (dict_search('client', openvpn) or []):
if client['ip']:
for v4PoolNet in v4PoolNets:
if IPv4Address(client['ip'][0]) in v4PoolNet:
- print(f'Warning: Client "{client["name"]}" IP {client["ip"][0]} is in server IP pool, it is not reserved for this client.',
- file=stderr)
-
- if openvpn['server_ipv6_subnet']:
- if not openvpn['server_subnet']:
- raise ConfigError('IPv6 server requires an IPv4 server subnet')
-
- if openvpn['server_ipv6_pool']:
- if not openvpn['server_pool']:
- raise ConfigError('IPv6 server pool requires an IPv4 server pool')
-
- if int(openvpn['server_ipv6_pool_prefixlen']) >= 112:
- raise ConfigError('IPv6 server pool must be larger than /112')
-
- v6PoolStart = IPv6Address(openvpn['server_ipv6_pool_base'])
- v6PoolStop = IPv6Network((v6PoolStart, openvpn['server_ipv6_pool_prefixlen']), strict=False)[-1] # don't remove the parentheses, it's a 2-tuple
- v6PoolSize = int(v6PoolStop) - int(v6PoolStart) if int(openvpn['server_ipv6_pool_prefixlen']) > 96 else 65536
- if v6PoolSize < v4PoolSize:
- raise ConfigError(f'IPv6 server pool must be at least as large as the IPv4 pool (current sizes: IPv6={v6PoolSize} IPv4={v4PoolSize})')
-
- v6PoolNets = list(summarize_address_range(v6PoolStart, v6PoolStop))
- for client in openvpn['client']:
- if client['ipv6_ip']:
- for v6PoolNet in v6PoolNets:
- if IPv6Address(client['ipv6_ip'][0]) in v6PoolNet:
- print(f'Warning: Client "{client["name"]}" IP {client["ipv6_ip"][0]} is in server IP pool, it is not reserved for this client.',
- file=stderr)
-
- else:
- if openvpn['server_ipv6_push_route']:
- raise ConfigError('IPv6 push-route requires an IPv6 server subnet')
+ print(f'Warning: Client "{client["name"]}" IP {client["ip"][0]} is in server IP pool, it is not reserved for this client.')
+
+ for subnet in (dict_search('server.subnet', openvpn) or []):
+ if is_ipv6(subnet):
+ tmp = dict_search('client_ipv6_pool.base', openvpn)
+ if tmp:
+ if not dict_search('server.client_ip_pool', openvpn):
+ raise ConfigError('IPv6 server pool requires an IPv4 server pool')
+
+ if int(tmp.split('/')[1]) >= 112:
+ raise ConfigError('IPv6 server pool must be larger than /112')
+
+ #
+ # todo - weird logic
+ #
+ v6PoolStart = IPv6Address(tmp)
+ v6PoolStop = IPv6Network((v6PoolStart, openvpn['server_ipv6_pool_prefixlen']), strict=False)[-1] # don't remove the parentheses, it's a 2-tuple
+ v6PoolSize = int(v6PoolStop) - int(v6PoolStart) if int(openvpn['server_ipv6_pool_prefixlen']) > 96 else 65536
+ if v6PoolSize < v4PoolSize:
+ raise ConfigError(f'IPv6 server pool must be at least as large as the IPv4 pool (current sizes: IPv6={v6PoolSize} IPv4={v4PoolSize})')
+
+ v6PoolNets = list(summarize_address_range(v6PoolStart, v6PoolStop))
+ for client in (dict_search('client', openvpn) or []):
+ if client['ipv6_ip']:
+ for v6PoolNet in v6PoolNets:
+ if IPv6Address(client['ipv6_ip'][0]) in v6PoolNet:
+ print(f'Warning: Client "{client["name"]}" IP {client["ipv6_ip"][0]} is in server IP pool, it is not reserved for this client.')
- for client in openvpn ['client']:
- if client['ipv6_ip']:
- raise ConfigError(f'Server client "{client["name"]}" IPv6 IP requires an IPv6 server subnet')
- if client['ipv6_push_route']:
- raise ConfigError(f'Server client "{client["name"]} IPv6 push-route requires an IPv6 server subnet"')
- if client['ipv6_subnet']:
- raise ConfigError(f'Server client "{client["name"]} IPv6 subnet requires an IPv6 server subnet"')
+ else:
+ for route in (dict_search('server.push_route', openvpn) or []):
+ if is_ipv6(route):
+ raise ConfigError('IPv6 push-route requires an IPv6 server subnet')
+
+ #for client in openvpn ['client']:
+ # if client['ipv6_ip']:
+ # raise ConfigError(f'Server client "{client["name"]}" IPv6 IP requires an IPv6 server subnet')
+ # if client['ipv6_push_route']:
+ # raise ConfigError(f'Server client "{client["name"]} IPv6 push-route requires an IPv6 server subnet"')
+ # if client['ipv6_subnet']:
+ # raise ConfigError(f'Server client "{client["name"]} IPv6 subnet requires an IPv6 server subnet"')
else:
# checks for both client and site-to-site go here
- if openvpn['server_reject_unconfigured']:
- raise ConfigError('reject-unconfigured-clients is only supported in OpenVPN server mode')
-
- if openvpn['server_topology']:
- raise ConfigError('The "topology" option is only valid in server mode')
+ if dict_search('server.reject_unconfigured_clients', openvpn):
+ raise ConfigError('Option reject-unconfigured-clients only supported in server mode')
- if (not openvpn['remote_host']) and openvpn['redirect_gateway']:
+ if 'replace_default_route' in openvpn and 'remote_host' not in openvpn:
raise ConfigError('Cannot set "replace-default-route" without "remote-host"')
#
@@ -849,133 +310,135 @@ def verify(openvpn):
#
# verify specified IP address is present on any interface on this system
- if openvpn['local_host']:
+ if 'local_host' in openvpn:
if not is_addr_assigned(openvpn['local_host']):
- raise ConfigError('No interface on system with specified local-host IP address: {}'.format(openvpn['local_host']))
+ raise ConfigError('local-host IP address "{local_host}" not assigned' \
+ ' to any interface'.format(**openvpn))
# TCP active
if openvpn['protocol'] == 'tcp-active':
- if openvpn['local_port']:
+ if 'local_port' in openvpn:
raise ConfigError('Cannot specify "local-port" with "tcp-active"')
- if not openvpn['remote_host']:
+ if 'remote_host' in openvpn:
raise ConfigError('Must specify "remote-host" with "tcp-active"')
# shared secret and TLS
- if not (openvpn['shared_secret_file'] or openvpn['tls']):
+ if not ('shared_secret_key_file' in openvpn or 'tls' in openvpn):
raise ConfigError('Must specify one of "shared-secret-key-file" and "tls"')
- if openvpn['shared_secret_file'] and openvpn['tls']:
+ if {'shared_secret_key_file', 'tls'} <= set(openvpn):
raise ConfigError('Can only specify one of "shared-secret-key-file" and "tls"')
if openvpn['mode'] in ['client', 'server']:
- if not openvpn['tls']:
- raise ConfigError('Must specify "tls" in client-server mode')
+ if 'tls' not in openvpn:
+ raise ConfigError('Must specify "tls" for server and client mode')
#
# TLS/encryption
#
- if openvpn['shared_secret_file']:
- if openvpn['encryption'] in ['aes128gcm', 'aes192gcm', 'aes256gcm']:
- raise ConfigError('GCM encryption with shared-secret-key-file is not supported')
+ if 'shared_secret_key_file' in openvpn:
+ if dict_search('encryption.cipher', openvpn) in ['aes128gcm', 'aes192gcm', 'aes256gcm']:
+ raise ConfigError('GCM encryption with shared-secret-key-file not supported')
- if not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', openvpn['shared_secret_file']):
- raise ConfigError('Specified shared-secret-key-file "{}" is not valid'.format(openvpn['shared_secret_file']))
+ file = dict_search('shared_secret_key_file', openvpn)
+ if file and not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', file):
+ raise ConfigError(f'Specified shared-secret-key-file "{file}" is not valid')
- if openvpn['tls']:
- if not openvpn['tls_ca_cert']:
+ if 'tls' in openvpn:
+ if 'ca_cert_file' not in openvpn['tls']:
raise ConfigError('Must specify "tls ca-cert-file"')
- if not (openvpn['mode'] == 'client' and openvpn['auth']):
- if not openvpn['tls_cert']:
- raise ConfigError('Must specify "tls cert-file"')
+ if not (openvpn['mode'] == 'client' and 'auth_file' in openvpn['tls']):
+ if 'cert_file' not in openvpn['tls']:
+ raise ConfigError('Missing "tls cert-file"')
- if not openvpn['tls_key']:
- raise ConfigError('Must specify "tls key-file"')
+ if 'key_file' not in openvpn['tls']:
+ raise ConfigError('Missing "tls key-file"')
- if openvpn['tls_auth'] and openvpn['tls_crypt']:
+ if {'auth_file', 'crypt_file'} <= set(openvpn['tls']):
raise ConfigError('TLS auth and crypt are mutually exclusive')
- if not checkCertHeader('-----BEGIN CERTIFICATE-----', openvpn['tls_ca_cert']):
- raise ConfigError('Specified ca-cert-file "{}" is invalid'.format(openvpn['tls_ca_cert']))
+ file = dict_search('tls.ca_cert_file', openvpn)
+ if file and not checkCertHeader('-----BEGIN CERTIFICATE-----', file):
+ raise ConfigError(f'Specified ca-cert-file "{file}" is invalid')
- if openvpn['tls_auth']:
- if not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', openvpn['tls_auth']):
- raise ConfigError('Specified auth-file "{}" is invalid'.format(openvpn['tls_auth']))
+ file = dict_search('tls.auth_file', openvpn)
+ if file and not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', file):
+ raise ConfigError(f'Specified auth-file "{file}" is invalid')
- if openvpn['tls_cert']:
- if not checkCertHeader('-----BEGIN CERTIFICATE-----', openvpn['tls_cert']):
- raise ConfigError('Specified cert-file "{}" is invalid'.format(openvpn['tls_cert']))
+ file = dict_search('tls.cert_file', openvpn)
+ if file and not checkCertHeader('-----BEGIN CERTIFICATE-----', file):
+ raise ConfigError(f'Specified cert-file "{file}" is invalid')
- if openvpn['tls_key']:
- if not checkCertHeader('-----BEGIN (?:RSA |EC )?PRIVATE KEY-----', openvpn['tls_key']):
- raise ConfigError('Specified key-file "{}" is not valid'.format(openvpn['tls_key']))
+ file = dict_search('tls.key_file', openvpn)
+ if file and not checkCertHeader('-----BEGIN (?:RSA |EC )?PRIVATE KEY-----', file):
+ raise ConfigError(f'Specified key-file "{file}" is not valid')
- if openvpn['tls_crypt']:
- if not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', openvpn['tls_crypt']):
- raise ConfigError('Specified TLS crypt-file "{}" is invalid'.format(openvpn['tls_crypt']))
+ file = dict_search('tls.crypt_file', openvpn)
+ if file and not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', file):
+ raise ConfigError(f'Specified TLS crypt-file "{file}" is invalid')
- if openvpn['tls_crl']:
- if not checkCertHeader('-----BEGIN X509 CRL-----', openvpn['tls_crl']):
- raise ConfigError('Specified crl-file "{} not valid'.format(openvpn['tls_crl']))
+ file = dict_search('tls.crl_file', openvpn)
+ if file and not checkCertHeader('-----BEGIN X509 CRL-----', file):
+ raise ConfigError(f'Specified crl-file "{file} not valid')
- if openvpn['tls_dh'] and openvpn['tls_dh'] != 'none':
- if not checkCertHeader('-----BEGIN DH PARAMETERS-----', openvpn['tls_dh']):
- raise ConfigError('Specified dh-file "{}" is not valid'.format(openvpn['tls_dh']))
+ file = dict_search('tls.dh_file', openvpn)
+ if file and not checkCertHeader('-----BEGIN DH PARAMETERS-----', file):
+ raise ConfigError(f'Specified dh-file "{file}" is not valid')
- if openvpn['tls_role']:
+ if file and not verify_diffie_hellman_length(file, 2048):
+ raise ConfigError(f'Minimum DH key-size is 2048 bits')
+
+ tmp = dict_search('tls.role', openvpn)
+ if tmp:
if openvpn['mode'] in ['client', 'server']:
- if not openvpn['tls_auth']:
+ if not dict_search('tls.auth_file', openvpn):
raise ConfigError('Cannot specify "tls role" in client-server mode')
- if openvpn['tls_role'] == 'active':
+ if tmp == 'active':
if openvpn['protocol'] == 'tcp-passive':
raise ConfigError('Cannot specify "tcp-passive" when "tls role" is "active"')
- if openvpn['tls_dh'] and openvpn['tls_dh'] != 'none':
+ if dict_search('tls.dh_file', openvpn):
raise ConfigError('Cannot specify "tls dh-file" when "tls role" is "active"')
- elif openvpn['tls_role'] == 'passive':
+ elif tmp == 'passive':
if openvpn['protocol'] == 'tcp-active':
raise ConfigError('Cannot specify "tcp-active" when "tls role" is "passive"')
- if not openvpn['tls_dh']:
+ if not dict_search('tls.dh_file', openvpn):
raise ConfigError('Must specify "tls dh-file" when "tls role" is "passive"')
- if openvpn['tls_key'] and checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls_key']):
- if openvpn['tls_dh'] and openvpn['tls_dh'] != 'none':
- print('Warning: using dh-file and EC keys simultaneously will lead to DH ciphers being used instead of ECDH')
- else:
- print('Diffie-Hellman prime file is unspecified, assuming ECDH')
+ file = dict_search('tls.key_file', openvpn)
+ if file and checkCertHeader('-----BEGIN EC PRIVATE KEY-----', file):
+ if dict_search('tls.dh_file', openvpn):
+ print('Warning: using dh-file and EC keys simultaneously will ' \
+ 'lead to DH ciphers being used instead of ECDH')
- if openvpn['encryption'] == 'none':
- print('Warning: "encryption none" was specified. NO encryption will be performed and tunnelled data WILL be transmitted in clear text over the network!')
+ if dict_search('encryption.cipher', openvpn) == 'none':
+ print('Warning: "encryption none" was specified!')
+ print('No encryption will be performed and data is transmitted in ' \
+ 'plain text over the network!')
#
# Auth user/pass
#
- if openvpn['auth']:
- if not openvpn['auth_user']:
- raise ConfigError('Username for authentication is missing')
-
- if not openvpn['auth_pass']:
+ if (dict_search('authentication.username', openvpn) and not
+ dict_search('authentication.password', openvpn)):
raise ConfigError('Password for authentication is missing')
- if openvpn['vrf']:
- if openvpn['vrf'] not in interfaces():
- raise ConfigError(f'VRF "{openvpn["vrf"]}" does not exist')
+ if (dict_search('authentication.password', openvpn) and not
+ dict_search('authentication.username', openvpn)):
+ raise ConfigError('Username for authentication is missing')
- if openvpn['is_bridge_member']:
- raise ConfigError((
- f'Interface "{openvpn["intf"]}" cannot be member of VRF '
- f'"{openvpn["vrf"]}" and bridge "{openvpn["is_bridge_member"]}" '
- f'at the same time!'))
+ verify_vrf(openvpn)
return None
def generate(openvpn):
- interface = openvpn['intf']
- directory = os.path.dirname(get_config_name(interface))
+ interface = openvpn['ifname']
+ directory = os.path.dirname(cfg_file.format(**openvpn))
# we can't know in advance which clients have been removed,
# thus all client configs will be removed and re-added on demand
@@ -983,25 +446,20 @@ def generate(openvpn):
if os.path.isdir(ccd_dir):
rmtree(ccd_dir, ignore_errors=True)
- if openvpn['deleted'] or openvpn['disable']:
+ if 'deleted' in openvpn or 'disable' in openvpn:
return None
- # create config directory on demand
- directories = []
- directories.append(f'{directory}/status')
- directories.append(f'{directory}/ccd/{interface}')
- for onedir in directories:
- if not os.path.exists(onedir):
- os.makedirs(onedir, 0o755)
- chown(onedir, user, group)
-
# Fix file permissons for keys
fix_permissions = []
- fix_permissions.append(openvpn['shared_secret_file'])
- fix_permissions.append(openvpn['tls_key'])
+
+ tmp = dict_search('shared_secret_key_file', openvpn)
+ if tmp: fix_permissions.append(openvpn['shared_secret_key_file'])
+
+ tmp = dict_search('tls.key_file', openvpn)
+ if tmp: fix_permissions.append(tmp)
# Generate User/Password authentication file
- if openvpn['auth']:
+ if 'auth' in openvpn:
with open(openvpn['auth_user_pass_file'], 'w') as f:
f.write('{}\n{}'.format(openvpn['auth_user'], openvpn['auth_pass']))
# also change permission on auth file
@@ -1013,16 +471,20 @@ def generate(openvpn):
os.remove(openvpn['auth_user_pass_file'])
# Generate client specific configuration
- for client in openvpn['client']:
- client_file = os.path.join(ccd_dir, client['name'])
- render(client_file, 'openvpn/client.conf.tmpl', client)
- chown(client_file, user, group)
+ if dict_search('server.client', openvpn):
+ for client, client_config in dict_search('server.client', openvpn).items():
+ client_file = os.path.join(ccd_dir, client)
+
+ # Our client need's to know its subnet mask ...
+ client_config['subnet'] = dict_search('server.subnet', openvpn)
+ render(client_file, 'openvpn/client.conf.tmpl', client_config,
+ trim_blocks=True, user=user, group=group)
# we need to support quoting of raw parameters from OpenVPN CLI
# see https://phabricator.vyos.net/T1632
- render(get_config_name(interface), 'openvpn/server.conf.tmpl', openvpn,
- formater=lambda _: _.replace("&quot;", '"'))
- chown(get_config_name(interface), user, group)
+ render(cfg_file.format(**openvpn), 'openvpn/server.conf.tmpl', openvpn,
+ trim_blocks=True, formater=lambda _: _.replace("&quot;", '"'),
+ user=user, group=group)
# Fixup file permissions
for file in fix_permissions:
@@ -1031,75 +493,34 @@ def generate(openvpn):
return None
def apply(openvpn):
- interface = openvpn['intf']
+ interface = openvpn['ifname']
call(f'systemctl stop openvpn@{interface}.service')
- # On configuration change we need to wait for the 'old' interface to
- # vanish from the Kernel, if it is not gone, OpenVPN will report:
- # ERROR: Cannot ioctl TUNSETIFF vtun10: Device or resource busy (errno=16)
- if interface in interfaces():
- cmd(f'sudo ip link del {interface}')
-
# Do some cleanup when OpenVPN is disabled/deleted
- if openvpn['deleted'] or openvpn['disable']:
+ if 'deleted' in openvpn or 'disable' in openvpn:
# cleanup old configuration files
cleanup = []
- cleanup.append(get_config_name(interface))
+ cleanup.append(cfg_file.format(**openvpn))
cleanup.append(openvpn['auth_user_pass_file'])
for file in cleanup:
if os.path.isfile(file):
os.unlink(file)
+ if interface in interfaces():
+ VTunIf(interface).remove()
+
return None
# No matching OpenVPN process running - maybe it got killed or none
# existed - nevertheless, spawn new OpenVPN process
call(f'systemctl start openvpn@{interface}.service')
- if interface not in interfaces():
- try:
- dev_type = openvpn['type']
- cmd(f'sudo openvpn --mktun --dev-type {dev_type} --dev {interface}')
- except:
- pass
-
- # we need to catch the exception if the interface is not up due to
- # reason stated above
- o = VTunIf(interface)
- # update interface description used e.g. within SNMP
- o.set_alias(openvpn['description'])
- # IPv6 accept RA
- o.set_ipv6_accept_ra(openvpn['ipv6_accept_ra'])
- # IPv6 address autoconfiguration
- o.set_ipv6_autoconf(openvpn['ipv6_autoconf'])
- # IPv6 forwarding
- o.set_ipv6_forwarding(openvpn['ipv6_forwarding'])
- # IPv6 Duplicate Address Detection (DAD) tries
- o.set_ipv6_dad_messages(openvpn['ipv6_dup_addr_detect'])
-
- # IPv6 EUI-based addresses - only in TAP mode (TUN's have no MAC)
- # If MAC has changed, old EUI64 addresses won't get deleted,
- # but this isn't easy to solve, so leave them.
- # This is even more difficult as openvpn uses a random MAC for the
- # initial interface creation, unless set by 'lladdr'.
- # NOTE: right now the interface is always deleted. For future
- # compatibility when tap's are not deleted, leave the del_ in
- if openvpn['mode'] == 'tap':
- for addr in openvpn['ipv6_eui64_prefix_remove']:
- o.del_ipv6_eui64_address(addr)
- for addr in openvpn['ipv6_eui64_prefix']:
- o.add_ipv6_eui64_address(addr)
-
- # assign/remove VRF (ONLY when not a member of a bridge,
- # otherwise 'nomaster' removes it from it)
- if not openvpn['is_bridge_member']:
- o.set_vrf(openvpn['vrf'])
-
- # TAP interface needs to be brought up explicitly
- if openvpn['type'] == 'tap':
- if not openvpn['disable']:
- VTunIf(interface).set_admin_state('up')
+ conf = VTunIf.get_config()
+ conf['device_type'] = openvpn['device_type']
+
+ o = VTunIf(interface, **conf)
+ o.update(openvpn)
return None
@@ -1113,3 +534,4 @@ if __name__ == '__main__':
except ConfigError as e:
print(e)
exit(1)
+
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index c1770771e..a18a21b83 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -32,7 +32,7 @@ from vyos.configverify import verify_vrf
from vyos.ifconfig import WiFiIf
from vyos.template import render
from vyos.util import call
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -80,13 +80,13 @@ def get_config(config=None):
# 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)):
+ if not (dict_search('security.wpa.passphrase', tmp) or
+ 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):
+ if dict_search('security.wpa.radius.server.port', wifi):
del wifi['security']['wpa']['radius']['server']['port']
if not len(wifi['security']['wpa']['radius']['server']):
del wifi['security']['wpa']['radius']
@@ -119,10 +119,10 @@ def get_config(config=None):
if tmp: wifi['station_interfaces'] = tmp
# Add individual RADIUS server default values
- if vyos_dict_search('security.wpa.radius.server', wifi):
+ if dict_search('security.wpa.radius.server', wifi):
default_values = defaults(base + ['security', 'wpa', 'radius', 'server'])
- for server in vyos_dict_search('security.wpa.radius.server', wifi):
+ for server in dict_search('security.wpa.radius.server', wifi):
wifi['security']['wpa']['radius']['server'][server] = dict_merge(
default_values, wifi['security']['wpa']['radius']['server'][server])
@@ -241,7 +241,7 @@ def generate(wifi):
wifi['mac'] = str(mac)
# XXX: Jinja2 can not operate on a dictionary key when it starts of with a number
- if '40mhz_incapable' in (vyos_dict_search('capabilities.ht', wifi) or []):
+ if '40mhz_incapable' in (dict_search('capabilities.ht', wifi) or []):
wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable']
del wifi['capabilities']['ht']['40mhz_incapable']
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 1978adff5..957f72ed5 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -33,7 +33,7 @@ def get_config():
base = ['protocols', 'nbgp']
bgp = conf.get_config_dict(base, key_mangling=('-', '_'))
if not conf.exists(base):
- return None
+ bgp = {}
from pprint import pprint
pprint(bgp)
@@ -44,10 +44,16 @@ def verify(bgp):
if not bgp:
return None
+ # Check if declared more than one ASN
+ for asn in bgp['nbgp'].items():
+ if len(bgp['nbgp']) > 1:
+ raise ConfigError('Only one bgp ASN process can be definded')
+
return None
def generate(bgp):
if not bgp:
+ bgp['new_frr_config'] = ''
return None
# render(config) not needed, its only for debug
@@ -58,9 +64,6 @@ def generate(bgp):
return None
def apply(bgp):
- if bgp is None:
- return None
-
# Save original configration prior to starting any commit actions
bgp['original_config'] = frr.get_configuration(daemon='bgpd')
bgp['modified_config'] = frr.replace_section(bgp['original_config'], bgp['new_frr_config'], from_re='router bgp .*')
diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index 904d219e2..d2ff0a2ea 100755
--- a/src/conf_mode/protocols_mpls.py
+++ b/src/conf_mode/protocols_mpls.py
@@ -38,30 +38,32 @@ def get_config(config=None):
'router_id' : None,
'mpls_ldp' : False,
'old_ldp' : {
- 'interfaces' : [],
- 'neighbors' : {},
- 'd_transp_ipv4' : None,
- 'd_transp_ipv6' : None,
- 'hello_holdtime' : None,
- 'hello_interval' : None,
- 'ses_ipv4_hold' : None,
- 'ses_ipv6_hold' : None,
- 'export_ipv4_exp' : False,
- 'export_ipv6_exp' : False
-
+ 'interfaces' : [],
+ 'neighbors' : {},
+ 'd_transp_ipv4' : None,
+ 'd_transp_ipv6' : None,
+ 'hello_ipv4_holdtime' : None,
+ 'hello_ipv4_interval' : None,
+ 'hello_ipv6_holdtime' : None,
+ 'hello_ipv6_interval' : None,
+ 'ses_ipv4_hold' : None,
+ 'ses_ipv6_hold' : None,
+ 'export_ipv4_exp' : False,
+ 'export_ipv6_exp' : False
},
'ldp' : {
- 'interfaces' : [],
- 'neighbors' : {},
- 'd_transp_ipv4' : None,
- 'd_transp_ipv6' : None,
- 'hello_holdtime' : None,
- 'hello_interval' : None,
- 'ses_ipv4_hold' : None,
- 'ses_ipv6_hold' : None,
- 'export_ipv4_exp' : False,
- 'export_ipv6_exp' : False
-
+ 'interfaces' : [],
+ 'neighbors' : {},
+ 'd_transp_ipv4' : None,
+ 'd_transp_ipv6' : None,
+ 'hello_ipv4_holdtime' : None,
+ 'hello_ipv4_interval' : None,
+ 'hello_ipv6_holdtime' : None,
+ 'hello_ipv6_interval' : None,
+ 'ses_ipv4_hold' : None,
+ 'ses_ipv6_hold' : None,
+ 'export_ipv4_exp' : False,
+ 'export_ipv6_exp' : False
}
}
if not (conf.exists('protocols mpls') or conf.exists_effective('protocols mpls')):
@@ -78,19 +80,33 @@ def get_config(config=None):
if conf.exists('router-id'):
mpls_conf['router_id'] = conf.return_value('router-id')
- # Get hello holdtime
- if conf.exists_effective('discovery hello-holdtime'):
- mpls_conf['old_ldp']['hello_holdtime'] = conf.return_effective_value('discovery hello-holdtime')
+ # Get hello-ipv4-holdtime
+ if conf.exists_effective('discovery hello-ipv4-holdtime'):
+ mpls_conf['old_ldp']['hello_ipv4_holdtime'] = conf.return_effective_value('discovery hello-ipv4-holdtime')
+
+ if conf.exists('discovery hello-ipv4-holdtime'):
+ mpls_conf['ldp']['hello_ipv4_holdtime'] = conf.return_value('discovery hello-ipv4-holdtime')
+
+ # Get hello-ipv4-interval
+ if conf.exists_effective('discovery hello-ipv4-interval'):
+ mpls_conf['old_ldp']['hello_ipv4_interval'] = conf.return_effective_value('discovery hello-ipv4-interval')
+
+ if conf.exists('discovery hello-ipv4-interval'):
+ mpls_conf['ldp']['hello_ipv4_interval'] = conf.return_value('discovery hello-ipv4-interval')
+
+ # Get hello-ipv6-holdtime
+ if conf.exists_effective('discovery hello-ipv6-holdtime'):
+ mpls_conf['old_ldp']['hello_ipv6_holdtime'] = conf.return_effective_value('discovery hello-ipv6-holdtime')
- if conf.exists('discovery hello-holdtime'):
- mpls_conf['ldp']['hello_holdtime'] = conf.return_value('discovery hello-holdtime')
+ if conf.exists('discovery hello-ipv6-holdtime'):
+ mpls_conf['ldp']['hello_ipv6_holdtime'] = conf.return_value('discovery hello-ipv6-holdtime')
- # Get hello interval
- if conf.exists_effective('discovery hello-interval'):
- mpls_conf['old_ldp']['hello_interval'] = conf.return_effective_value('discovery hello-interval')
+ # Get hello-ipv6-interval
+ if conf.exists_effective('discovery hello-ipv6-interval'):
+ mpls_conf['old_ldp']['hello_ipv6_interval'] = conf.return_effective_value('discovery hello-ipv6-interval')
- if conf.exists('discovery hello-interval'):
- mpls_conf['ldp']['hello_interval'] = conf.return_value('discovery hello-interval')
+ if conf.exists('discovery hello-ipv6-interval'):
+ mpls_conf['ldp']['hello_ipv6_interval'] = conf.return_value('discovery hello-ipv6-interval')
# Get session-ipv4-holdtime
if conf.exists_effective('discovery session-ipv4-holdtime'):
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index a520120f8..2260b3fe1 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -23,7 +23,7 @@ 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 vyos_dict_search
+from vyos.util import dict_search
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -57,13 +57,13 @@ def verify(pppoe):
raise ConfigError('At least one listen interface must be defined!')
# 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
- vyos_dict_search('client_ip_pool.stop', pppoe))):
+ if not (dict_search('client_ip_pool.subnet', pppoe) or
+ (dict_search('client_ip_pool.start', pppoe) and
+ dict_search('client_ip_pool.stop', pppoe))):
print('Warning: No PPPoE client pool defined')
- if vyos_dict_search('authentication.radius.dynamic_author.server', pppoe):
- if not vyos_dict_search('authentication.radius.dynamic_author.key', pppoe):
+ if dict_search('authentication.radius.dynamic_author.server', pppoe):
+ if not dict_search('authentication.radius.dynamic_author.key', pppoe):
raise ConfigError('DA/CoE server key required!')
return None
@@ -75,7 +75,7 @@ def generate(pppoe):
render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe, trim_blocks=True)
- if vyos_dict_search('authentication.mode', pppoe) == 'local':
+ if dict_search('authentication.mode', pppoe) == 'local':
render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl',
pppoe, trim_blocks=True, permission=0o640)
else:
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index a19fa72d8..8d8e9e4c4 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -21,10 +21,11 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
-from vyos import ConfigError
+from vyos.configverify import verify_vrf
from vyos.util import call
from vyos.template import render
from vyos.xml import defaults
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -54,9 +55,7 @@ def verify(ssh):
if not ssh:
return None
- if 'vrf' in ssh.keys() and ssh['vrf'] not in interfaces():
- raise ConfigError('VRF "{vrf}" does not exist'.format(**ssh))
-
+ verify_vrf(ssh)
return None
def generate(ssh):
diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py
index da51b0d06..465986d5b 100755
--- a/src/conf_mode/vpn_l2tp.py
+++ b/src/conf_mode/vpn_l2tp.py
@@ -100,7 +100,8 @@ def get_config(config=None):
if conf.exists(['authentication', 'mode']):
l2tp['auth_mode'] = conf.return_value(['authentication', 'mode'])
- if conf.exists(['authentication', 'protocols']):
+ if conf.exists(['authentication', 'require']):
+ l2tp['auth_proto'] = []
auth_mods = {
'pap': 'auth_pap',
'chap': 'auth_chap_md5',
@@ -108,7 +109,7 @@ def get_config(config=None):
'mschap-v2': 'auth_mschap_v2'
}
- for proto in conf.return_values(['authentication', 'protocols']):
+ for proto in conf.return_values(['authentication', 'require']):
l2tp['auth_proto'].append(auth_mods[proto])
if conf.exists(['authentication', 'mppe']):
@@ -161,6 +162,9 @@ def get_config(config=None):
conf.set_level(base_path + ['authentication', 'radius', 'server', server])
+ if conf.exists(['disable-accounting']):
+ radius['acct_port'] = '0'
+
if conf.exists(['fail-time']):
radius['fail_time'] = conf.return_value(['fail-time'])
diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py
index 306d05c60..3125ee9d0 100755
--- a/src/conf_mode/vpn_pptp.py
+++ b/src/conf_mode/vpn_pptp.py
@@ -121,6 +121,9 @@ def get_config(config=None):
conf.set_level(base_path + ['authentication', 'radius', 'server', server])
+ if conf.exists(['disable-accounting']):
+ radius['acct_port'] = '0'
+
if conf.exists(['fail-time']):
radius['fail_time'] = conf.return_value(['fail-time'])
diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py
index 2597ba42f..1b2b80ce5 100755
--- a/src/conf_mode/vpn_sstp.py
+++ b/src/conf_mode/vpn_sstp.py
@@ -23,7 +23,7 @@ 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 vyos_dict_search
+from vyos.util import dict_search
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -56,21 +56,21 @@ def verify(sstp):
#
# SSL certificate checks
#
- tmp = vyos_dict_search('ssl.ca_cert_file', sstp)
+ tmp = 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!')
- tmp = vyos_dict_search('ssl.cert_file', sstp)
+ tmp = 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!')
- tmp = vyos_dict_search('ssl.key_file', sstp)
+ tmp = dict_search('ssl.key_file', sstp)
if not tmp:
raise ConfigError(f'SSL private key file required!')
else:
@@ -84,7 +84,7 @@ 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 vyos_dict_search('authentication.mode', sstp) == 'local':
+ if dict_search('authentication.mode', sstp) == 'local':
render(sstp_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl',
sstp, trim_blocks=True, permission=0o640)
else:
diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py
index c2da1bb11..e579e81b2 100755
--- a/src/helpers/vyos-load-config.py
+++ b/src/helpers/vyos-load-config.py
@@ -23,7 +23,9 @@ Example: load https://somewhere.net/some.config
load /tmp/some.config
"""
+import os
import sys
+import gzip
import tempfile
import vyos.defaults
import vyos.remote
@@ -37,35 +39,47 @@ class LoadConfig(ConfigSourceSession):
def load_config(self, path):
return self._run(['/bin/cli-shell-api','loadFile',path])
-
file_name = sys.argv[1] if len(sys.argv) > 1 else 'config.boot'
configdir = vyos.defaults.directories['config']
protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp']
+def get_local_config(filename):
+ if os.path.isfile(filename):
+ fname = filename
+ elif os.path.isfile(os.path.join(configdir, filename)):
+ fname = os.path.join(configdir, filename)
+ else:
+ sys.exit(f"No such file '{filename}'")
+
+ if fname.endswith('.gz'):
+ with gzip.open(fname, 'rb') as f:
+ try:
+ config_str = f.read().decode()
+ except OSError as e:
+ sys.exit(e)
+ else:
+ with open(fname, 'r') as f:
+ try:
+ config_str = f.read()
+ except OSError as e:
+ sys.exit(e)
+
+ return config_str
if any(x in file_name for x in protocols):
- config_file = vyos.remote.get_remote_config(file_name)
- if not config_file:
- sys.exit("No config file by that name.")
+ config_string = vyos.remote.get_remote_config(file_name)
+ if not config_string:
+ sys.exit(f"No such config file at '{file_name}'")
else:
- canonical_path = '{0}/{1}'.format(configdir, file_name)
- try:
- with open(canonical_path, 'r') as f:
- config_file = f.read()
- except OSError as err1:
- try:
- with open(file_name, 'r') as f:
- config_file = f.read()
- except OSError as err2:
- sys.exit('{0}\n{1}'.format(err1, err2))
+ config_string = get_local_config(file_name)
config = LoadConfig()
-print("Loading configuration from '{}'".format(file_name))
+print(f"Loading configuration from '{file_name}'")
with tempfile.NamedTemporaryFile() as fp:
with open(fp.name, 'w') as fd:
- fd.write(config_file)
+ fd.write(config_string)
virtual_migration = VirtualMigrator(fp.name)
try:
diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd
index 59dbeda17..4c4bb036e 100755
--- a/src/services/vyos-hostsd
+++ b/src/services/vyos-hostsd
@@ -589,7 +589,7 @@ if __name__ == '__main__':
socket = context.socket(zmq.REP)
# Set the right permissions on the socket, then change it back
- o_mask = os.umask(0o007)
+ o_mask = os.umask(0o000)
socket.bind(SOCKET_PATH)
os.umask(o_mask)
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index d5730d86c..703628558 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -261,7 +261,7 @@ def config_file_op(command):
path = command['file']
except KeyError:
return error(400, "Missing required field \"file\"")
- res = session.load_config(path)
+ res = session.migrate_and_load_config(path)
res = session.commit()
else:
return error(400, "\"{0}\" is not a valid operation".format(op))
diff --git a/src/tests/helper.py b/src/tests/helper.py
index a7e4f201c..f7033148a 100644
--- a/src/tests/helper.py
+++ b/src/tests/helper.py
@@ -13,13 +13,10 @@
#
# 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 sys
import importlib.util
-
def prepare_module(file_path='', module_name=''):
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
diff --git a/src/tests/test_config_parser.py b/src/tests/test_config_parser.py
index 5b922e2dd..6e0a071f8 100644
--- a/src/tests/test_config_parser.py
+++ b/src/tests/test_config_parser.py
@@ -15,11 +15,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import tempfile
-import unittest
+import vyos.configtree
from unittest import TestCase
-import vyos.configtree
class TestConfigParser(TestCase):
def setUp(self):
diff --git a/src/tests/test_configverify.py b/src/tests/test_configverify.py
new file mode 100644
index 000000000..ad7e053db
--- /dev/null
+++ b/src/tests/test_configverify.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from unittest import TestCase
+from vyos.configverify import verify_diffie_hellman_length
+from vyos.util import cmd
+
+dh_file = '/tmp/dh.pem'
+
+class TestDictSearch(TestCase):
+ def setUp(self):
+ pass
+
+ def test_dh_key_none(self):
+ self.assertFalse(verify_diffie_hellman_length('/tmp/non_existing_file', '1024'))
+
+ def test_dh_key_256(self):
+ key_len = '256'
+ cmd(f'openssl dhparam -out {dh_file} {key_len}')
+ self.assertTrue(verify_diffie_hellman_length(dh_file, key_len))
+
+ def test_dh_key_512(self):
+ key_len = '512'
+ cmd(f'openssl dhparam -out {dh_file} {key_len}')
+ self.assertTrue(verify_diffie_hellman_length(dh_file, key_len))
diff --git a/src/tests/test_vyos_dict_search.py b/src/tests/test_dict_search.py
index ef338d46f..6a0fc74ad 100644
--- a/src/tests/test_vyos_dict_search.py
+++ b/src/tests/test_dict_search.py
@@ -14,10 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import unittest
from unittest import TestCase
-
-from vyos.util import vyos_dict_search
+from vyos.util import dict_search
data = {
'string': 'fooo',
@@ -31,29 +29,29 @@ class TestDictSearch(TestCase):
pass
def test_non_existing_keys(self):
- """ TestDictSearch: Return False when querying for non-existent key """
- self.assertFalse(vyos_dict_search('non_existing', data))
+ # TestDictSearch: Return False when querying for non-existent key
+ self.assertFalse(dict_search('non_existing', data))
def test_string(self):
- """ TestDictSearch: Return value when querying string """
- self.assertEqual(vyos_dict_search('string', data), data['string'])
+ # TestDictSearch: Return value when querying string
+ self.assertEqual(dict_search('string', data), data['string'])
def test_list(self):
- """ TestDictSearch: Return list items when querying list """
- self.assertEqual(vyos_dict_search('list', data), data['list'])
+ # TestDictSearch: Return list items when querying list
+ self.assertEqual(dict_search('list', data), data['list'])
def test_dict_key_value(self):
- """ TestDictSearch: Return dictionary keys value when value is present """
- self.assertEqual(vyos_dict_search('dict.key_2', data), data['dict']['key_2'])
+ # TestDictSearch: Return dictionary keys value when value is present
+ self.assertEqual(dict_search('dict.key_2', data), data['dict']['key_2'])
def test_nested_dict_key_value(self):
- """ TestDictSearch: Return string value of last key when querying for a nested string """
- self.assertEqual(vyos_dict_search('nested.string', data), data['nested']['string'])
+ # TestDictSearch: Return string value of last key when querying for a nested string
+ self.assertEqual(dict_search('nested.string', data), data['nested']['string'])
def test_nested_dict_key_empty(self):
- """ TestDictSearch: Return False when querying for a nested string whose last key is empty """
- self.assertFalse(vyos_dict_search('nested.empty', data))
+ # TestDictSearch: Return False when querying for a nested string whose last key is empty
+ self.assertFalse(dict_search('nested.empty', data))
def test_nested_list(self):
- """ TestDictSearch: Return list items when querying nested list """
- self.assertEqual(vyos_dict_search('nested.list', data), data['nested']['list'])
+ # TestDictSearch: Return list items when querying nested list
+ self.assertEqual(dict_search('nested.list', data), data['nested']['list'])
diff --git a/src/tests/test_find_device_file.py b/src/tests/test_find_device_file.py
new file mode 100755
index 000000000..43c80dc76
--- /dev/null
+++ b/src/tests/test_find_device_file.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from unittest import TestCase
+from vyos.util import find_device_file
+
+class TestDeviceFile(TestCase):
+ """ used to find USB devices on target """
+ def setUp(self):
+ pass
+
+ def test_null(self):
+ self.assertEqual(find_device_file('null'), '/dev/null')
+
+ def test_zero(self):
+ self.assertEqual(find_device_file('zero'), '/dev/zero')
+
+ def test_input_event(self):
+ self.assertEqual(find_device_file('event0'), '/dev/input/event0')
+
+ def test_non_existing(self):
+ self.assertFalse(find_device_file('vyos'))
diff --git a/src/tests/test_jinja_filters.py b/src/tests/test_jinja_filters.py
new file mode 100644
index 000000000..acd7a5952
--- /dev/null
+++ b/src/tests/test_jinja_filters.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from unittest import TestCase
+
+from ipaddress import ip_network
+from vyos.template import vyos_address_from_cidr
+from vyos.template import vyos_netmask_from_cidr
+from vyos.template import vyos_ipv4
+from vyos.template import vyos_ipv6
+from vyos.template import vyos_first_host_address
+from vyos.template import vyos_last_host_address
+from vyos.template import vyos_inc_ip
+
+class TestTeamplteHelpers(TestCase):
+ def setUp(self):
+ pass
+
+ def test_helpers_from_cidr(self):
+ network_v4 = '192.0.2.0/26'
+ self.assertEqual(vyos_address_from_cidr(network_v4), str(ip_network(network_v4).network_address))
+ self.assertEqual(vyos_netmask_from_cidr(network_v4), str(ip_network(network_v4).netmask))
+
+ def test_helpers_ipv4(self):
+ self.assertTrue(vyos_ipv4('192.0.2.1'))
+ self.assertTrue(vyos_ipv4('192.0.2.0/24'))
+ self.assertTrue(vyos_ipv4('192.0.2.1/32'))
+ self.assertTrue(vyos_ipv4('10.255.1.2'))
+ self.assertTrue(vyos_ipv4('10.255.1.0/24'))
+ self.assertTrue(vyos_ipv4('10.255.1.2/32'))
+ self.assertFalse(vyos_ipv4('2001:db8::'))
+ self.assertFalse(vyos_ipv4('2001:db8::1'))
+ self.assertFalse(vyos_ipv4('2001:db8::/64'))
+
+ def test_helpers_ipv6(self):
+ self.assertFalse(vyos_ipv6('192.0.2.1'))
+ self.assertFalse(vyos_ipv6('192.0.2.0/24'))
+ self.assertFalse(vyos_ipv6('192.0.2.1/32'))
+ self.assertFalse(vyos_ipv6('10.255.1.2'))
+ self.assertFalse(vyos_ipv6('10.255.1.0/24'))
+ self.assertFalse(vyos_ipv6('10.255.1.2/32'))
+ self.assertTrue(vyos_ipv6('2001:db8::'))
+ self.assertTrue(vyos_ipv6('2001:db8::1'))
+ self.assertTrue(vyos_ipv6('2001:db8::1/64'))
+ self.assertTrue(vyos_ipv6('2001:db8::/32'))
+ self.assertTrue(vyos_ipv6('2001:db8::/64'))
+
+ def test_helpers_first_host_address(self):
+ self.assertEqual(vyos_first_host_address('10.0.0.0/24'), '10.0.0.1')
+ self.assertEqual(vyos_first_host_address('10.0.0.128/25'), '10.0.0.129')
+ self.assertEqual(vyos_first_host_address('10.0.0.200/29'), '10.0.0.201')
+
+ self.assertEqual(vyos_first_host_address('2001:db8::/64'), '2001:db8::')
+ self.assertEqual(vyos_first_host_address('2001:db8::/112'), '2001:db8::')
+ self.assertEqual(vyos_first_host_address('2001:db8::10/112'), '2001:db8::10')
+ self.assertEqual(vyos_first_host_address('2001:db8::100/112'), '2001:db8::100')
diff --git a/src/tests/test_util.py b/src/tests/test_util.py
index 09bf947b8..f7405cbde 100644
--- a/src/tests/test_util.py
+++ b/src/tests/test_util.py
@@ -14,10 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import unittest
from unittest import TestCase
-
-import vyos.util
+from vyos.util import mangle_dict_keys
class TestVyOSUtil(TestCase):
@@ -27,6 +25,6 @@ class TestVyOSUtil(TestCase):
def test_key_mangline(self):
data = {"foo-bar": {"baz-quux": None}}
expected_data = {"foo_bar": {"baz_quux": None}}
- new_data = vyos.util.mangle_dict_keys(data, '-', '_')
+ new_data = mangle_dict_keys(data, '-', '_')
self.assertEqual(new_data, expected_data)
diff --git a/src/tests/test_validate.py b/src/tests/test_validate.py
new file mode 100644
index 000000000..e9fe185ed
--- /dev/null
+++ b/src/tests/test_validate.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import vyos.validate
+from unittest import TestCase
+
+class TestVyOSValidate(TestCase):
+ def setUp(self):
+ pass
+
+ def test_is_ip(self):
+ self.assertTrue(vyos.validate.is_ip('192.0.2.1'))
+ self.assertTrue(vyos.validate.is_ip('2001:db8::1'))
+ self.assertFalse(vyos.validate.is_ip('VyOS'))
+
+ def test_is_ipv4(self):
+ self.assertTrue(vyos.validate.is_ipv4('192.0.2.1'))
+ self.assertTrue(vyos.validate.is_ipv4('192.0.2.0/24'))
+ self.assertTrue(vyos.validate.is_ipv4('192.0.2.1/32'))
+
+ self.assertFalse(vyos.validate.is_ipv4('2001:db8::1'))
+ self.assertFalse(vyos.validate.is_ipv4('2001:db8::/64'))
+ self.assertFalse(vyos.validate.is_ipv4('VyOS'))
+
+ def test_is_ipv6(self):
+ self.assertFalse(vyos.validate.is_ipv6('192.0.2.1'))
+ self.assertFalse(vyos.validate.is_ipv6('192.0.2.0/24'))
+ self.assertFalse(vyos.validate.is_ipv6('192.0.2.1/32'))
+ self.assertTrue(vyos.validate.is_ipv6('2001:db8::1'))
+ self.assertTrue(vyos.validate.is_ipv6('2001:db8::/64'))
+ self.assertTrue(vyos.validate.is_ipv6('2001:db8::1/64'))
+ self.assertFalse(vyos.validate.is_ipv6('VyOS'))
+
+ def test_is_ipv6_link_local(self):
+ self.assertFalse(vyos.validate.is_ipv6_link_local('169.254.0.1'))
+ self.assertTrue(vyos.validate.is_ipv6_link_local('fe80::'))
+ self.assertTrue(vyos.validate.is_ipv6_link_local('fe80::affe:1'))
+ self.assertFalse(vyos.validate.is_ipv6_link_local('2001:db8::'))
+ self.assertFalse(vyos.validate.is_ipv6_link_local('VyOS'))
+
+ def test_is_ipv6_link_local(self):
+ self.assertTrue(vyos.validate.is_loopback_addr('127.0.0.1'))
+ self.assertTrue(vyos.validate.is_loopback_addr('127.0.1.1'))
+ self.assertTrue(vyos.validate.is_loopback_addr('127.1.1.1'))
+ self.assertTrue(vyos.validate.is_loopback_addr('::1'))
+
+ self.assertFalse(vyos.validate.is_loopback_addr('::2'))
+ self.assertFalse(vyos.validate.is_loopback_addr('192.0.2.1'))
+
+
+
diff --git a/vyos-configtest b/vyos-configtest
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/vyos-configtest