diff options
116 files changed, 3449 insertions, 2030 deletions
@@ -54,6 +54,7 @@ interface_definitions: $(BUILD_DIR) $(obj) rm -f $(TMPL_DIR)/interfaces/ethernet/node.tag/vif-s/node.tag/ipv6/node.def rm -f $(TMPL_DIR)/interfaces/ethernet/node.tag/vif-s/node.tag/vif-c/node.tag/ip/node.def rm -f $(TMPL_DIR)/interfaces/ethernet/node.tag/vif-s/node.tag/vif-c/node.tag/ipv6/node.def + rm -f $(TMPL_DIR)/interfaces/l2tpv3/node.tag/ip/node.def rm -f $(TMPL_DIR)/interfaces/l2tpv3/node.tag/ipv6/node.def rm -f $(TMPL_DIR)/interfaces/openvpn/node.tag/ipv6/node.def rm -f $(TMPL_DIR)/interfaces/pppoe/node.tag/ip/node.def diff --git a/data/configd-include.json b/data/configd-include.json index 95aef65ad..da6fb915f 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -48,7 +48,6 @@ "system-options.py", "system-syslog.py", "system-timezone.py", -"system-wifi-regdom.py", "system_console.py", "system_lcd.py", "task_scheduler.py", @@ -59,4 +58,4 @@ "vrf.py", "vrrp.py", "vyos_cert.py" -]
\ No newline at end of file +] diff --git a/data/templates/dhcp-client/ipv4.tmpl b/data/templates/dhcp-client/ipv4.tmpl index 8a44a9761..71b429db6 100644 --- a/data/templates/dhcp-client/ipv4.tmpl +++ b/data/templates/dhcp-client/ipv4.tmpl @@ -12,8 +12,13 @@ interface "{{ ifname }}" { {% if dhcp_options.vendor_class_id is defined and dhcp_options.vendor_class_id is not none %} send vendor-class-identifier "{{ dhcp_options.vendor_class_id }}"; {% endif %} - request subnet-mask, broadcast-address, routers, domain-name-servers, + # The request statement causes the client to request that any server responding to the + # client send the client its values for the specified options. + request subnet-mask, broadcast-address,{{ " routers," if dhcp_options.no_default_route is not defined }} domain-name-servers, rfc3442-classless-static-routes, domain-name, interface-mtu; + + # The require statement lists options that must be sent in order for an offer to be + # accepted. Offers that do not contain all the listed options will be ignored! require subnet-mask; } 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..4b7e5c5ea 100644 --- a/data/templates/frr/ldpd.frr.tmpl +++ b/data/templates/frr/ldpd.frr.tmpl @@ -7,12 +7,45 @@ no router-id {{ old_router_id }} {% if router_id -%} router-id {{ router_id }} {% endif -%} +{% if old_ldp.cisco_interop_tlv -%} +no dual-stack cisco-interop +{% endif -%} +{% if ldp.cisco_interop_tlv -%} +dual-stack cisco-interop +{% endif -%} +{% if old_ldp.transport_prefer_ipv4 -%} +no dual-stack transport-connection prefer ipv4 +{% endif -%} +{% if ldp.transport_prefer_ipv4 -%} +dual-stack transport-connection prefer ipv4 +{% endif -%} {% for neighbor_id in old_ldp.neighbors -%} no neighbor {{neighbor_id}} password {{old_ldp.neighbors[neighbor_id].password}} +{% if 'ttl_security' is defined -%} +{% if 'disable' in old_ldp.neighbors[neighbor_id].ttl_security %} +no neighbor {{neighbor_id}} ttl-security disable +{% else -%} +no neighbor {{neighbor_id}} ttl-security hops {{old_ldp.neighbors[neighbor_id].ttl_security}} +{% endif -%} +{% endif -%} +{% if 'session_holdtime' is defined -%} +no neighbor {{neighbor_id}} session holdtime {{old_ldp.neighbors[neighbor_id].session_holdtime}} +{% endif -%} {% endfor -%} {% for neighbor_id in ldp.neighbors -%} neighbor {{neighbor_id}} password {{ldp.neighbors[neighbor_id].password}} +{% if 'ttl_security' is defined -%} +{% if 'disable' in ldp.neighbors[neighbor_id].ttl_security %} +neighbor {{neighbor_id}} ttl-security disable +{% else -%} +neighbor {{neighbor_id}} ttl-security hops {{ldp.neighbors[neighbor_id].ttl_security}} +{% endif -%} +{% endif -%} +{% if 'session_holdtime' is defined -%} +neighbor {{neighbor_id}} session holdtime {{ldp.neighbors[neighbor_id].session_holdtime}} +{% endif -%} {% endfor -%} +! address-family ipv4 label local allocate host-routes {% if old_ldp.export_ipv4_exp -%} @@ -27,24 +60,48 @@ 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 }} {% endif -%} +{% if old_ldp.target_ipv4_enable -%} +no discovery targeted-hello accept +{% endif -%} +{% if ldp.target_ipv4_enable -%} +discovery targeted-hello accept +{% endif -%} +{% if old_ldp.target_ipv4_hello_int -%} +no discovery targeted-hello interval {{ old_ldp.target_ipv4_hello_int }} +{% endif -%} +{% if ldp.target_ipv4_hello_int -%} +discovery targeted-hello interval {{ ldp.target_ipv4_hello_int }} +{% endif -%} +{% if old_ldp.target_ipv4_hello_hold -%} +no discovery targeted-hello holdtime {{ old_ldp.target_ipv4_hello_hold }} +{% endif -%} +{% if ldp.target_ipv4_hello_hold -%} +discovery targeted-hello holdtime {{ ldp.target_ipv4_hello_hold }} +{% endif -%} +{% for address in old_ldp.target_ipv4_addresses -%} +no neighbor {{address}} targeted +{% endfor -%} +{% for address in ldp.target_ipv4_addresses -%} +neighbor {{address}} targeted +{% endfor -%} {% for interface in old_ldp.interfaces -%} no interface {{interface}} {% endfor -%} @@ -65,7 +122,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 +133,42 @@ 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 -%} +{% if old_ldp.target_ipv6_enable -%} +no discovery targeted-hello accept +{% endif -%} +{% if ldp.target_ipv6_enable -%} +discovery targeted-hello accept +{% endif -%} +{% if old_ldp.target_ipv6_hello_int -%} +no discovery targeted-hello interval {{ old_ldp.target_ipv6_hello_int }} +{% endif -%} +{% if ldp.target_ipv6_hello_int -%} +discovery targeted-hello interval {{ ldp.target_ipv6_hello_int }} +{% endif -%} +{% if old_ldp.target_ipv6_hello_hold -%} +no discovery targeted-hello holdtime {{ old_ldp.target_ipv6_hello_hold }} +{% endif -%} +{% if ldp.target_ipv6_hello_hold -%} +discovery targeted-hello holdtime {{ ldp.target_ipv6_hello_hold }} +{% endif -%} +{% for address in old_ldp.target_ipv6_addresses -%} +no neighbor {{address}} targeted +{% endfor -%} +{% for address in ldp.target_ipv6_addresses -%} +neighbor {{address}} targeted +{% endfor -%} {% for interface in old_ldp.interfaces -%} no interface {{interface}} {% endfor -%} @@ -91,4 +184,4 @@ no address-family ipv6 {% else -%} no mpls ldp {% endif -%} -! +!
\ No newline at end of file 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..62387ef7c 100644 --- a/data/templates/openvpn/client.conf.tmpl +++ b/data/templates/openvpn/client.conf.tmpl @@ -1,35 +1,31 @@ ### 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] }} {{ server_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 -%} - -{% if disable -%} +{% endfor %} +{% endif %} +{% 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..a510c3a84 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -2,246 +2,238 @@ # # 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 %} +{% if protocol == 'tcp-active' %} +proto tcp6-client +{% elif protocol == 'tcp-passive' %} +proto tcp6-server +{% else %} +proto udp6 +{% endif %} +{% 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 %} +{% 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 %} +{% for subnet in server.subnet if subnet | is_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 | dec_ip('1') }} {{ 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 | dec_ip('1') }} {{ subnet | netmask_from_cidr if device_type == 'tun' else '' }} +{% endif %} +{% endfor %} +{% 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 | is_ipv4 %} +{% if laddr_conf is defined and laddr_conf.subnet_mask is defined and laddr_conf.subnet_mask is not none %} +ifconfig {{ laddr }} {{ laddr_conf.subnet_mask }} +{% else %} +{% for raddr in remote_address %} +{% if raddr | is_ipv4 %} +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 +249,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/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl index c5e4240d1..16d9f7c98 100644 --- a/data/templates/wifi/hostapd.conf.tmpl +++ b/data/templates/wifi/hostapd.conf.tmpl @@ -23,7 +23,10 @@ interface={{ ifname }} # added to the bridge automatically (brctl may refuse to do this before hostapd # has been started to change the interface mode). If needed, the bridge # interface is also created. -bridge={{ is_bridge_member }} +{# as there can only be one bridge interface it is save to loop #} +{% for bridge in is_bridge_member %} +bridge={{ bridge }} +{% endfor %} {% endif %} # Driver interface type (hostap/wired/none/nl80211/bsd); @@ -69,18 +72,18 @@ ssid={{ ssid }} channel={{ channel }} {% endif %} -{% if mode %} +{% if mode is defined and mode is not none %} # Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz), # g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used # with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this -# needs to be set to hw_mode=a. For IEEE 802.11ax (HE) on 6 GHz this needs -# to be set to hw_mode=a. When using ACS (see channel parameter), a +# needs to be set to hw_mode a. For IEEE 802.11ax (HE) on 6 GHz this needs +# to be set to hw_mode a. When using ACS (see channel parameter), a # special value "any" can be used to indicate that any support band can be used. # This special case is currently supported only with drivers with which # offloaded ACS is used. -{% if 'n' in mode %} +{% if mode == 'n' %} hw_mode=g -{% elif 'ac' in mode %} +{% elif mode == 'ac' %} hw_mode=a ieee80211h=1 ieee80211ac=1 @@ -529,10 +532,13 @@ wep_key{{ loop.index -1 }}={{ security.wep.key }} # and/or WPA2 (full IEEE 802.11i/RSN): # bit0 = WPA # bit1 = IEEE 802.11i/RSN (WPA2) (dot11RSNAEnabled) +# Note that WPA3 is also configured with bit1 since it uses RSN just like WPA2. +# In other words, for WPA3, wpa 2 is used the configuration (and +# wpa_key_mgmt=SAE for WPA3-Personal instead of wpa_key_mgmt=WPA-PSK). {% if security.wpa.mode is defined %} -{% if security.wpa.mode == 'both' %} +{% if security.wpa.mode == 'wpa+wpa2' %} wpa=3 -{% elif security.wpa.mode == 'wpa2' %} +{% elif security.wpa.mode == 'wpa2' or security.wpa.mode == 'wpa3' %} wpa=2 {% elif security.wpa.mode == 'wpa' %} wpa=1 @@ -592,7 +598,15 @@ wpa_passphrase={{ security.wpa.passphrase }} # added to enable SHA256-based stronger algorithms. # WPA-PSK = WPA-Personal / WPA2-Personal # WPA-PSK-SHA256 = WPA2-Personal using SHA256 -wpa_key_mgmt=WPA-PSK +# WPA-EAP = WPA-Enterprise / WPA2-Enterprise +# WPA-EAP-SHA256 = WPA2-Enterprise using SHA256 +# SAE = SAE (WPA3-Personal) +# WPA-EAP-SUITE-B-192 = WPA3-Enterprise with 192-bit security/CNSA suite +{% if security.wpa.mode is defined and security.wpa.mode == 'wpa3' %} +wpa_key_mgmt=SAE +{% else %} +wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 +{% endif %} {% elif security.wpa.radius is defined %} ##### IEEE 802.1X-2004 related configuration ################################## @@ -602,9 +616,17 @@ ieee8021x=1 # Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The # entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be # added to enable SHA256-based stronger algorithms. +# WPA-PSK = WPA-Personal / WPA2-Personal +# WPA-PSK-SHA256 = WPA2-Personal using SHA256 # WPA-EAP = WPA-Enterprise / WPA2-Enterprise # WPA-EAP-SHA256 = WPA2-Enterprise using SHA256 -wpa_key_mgmt=WPA-EAP +# SAE = SAE (WPA3-Personal) +# WPA-EAP-SUITE-B-192 = WPA3-Enterprise with 192-bit security/CNSA suite +{% if security.wpa.mode is defined and security.wpa.mode == 'wpa3' %} +wpa_key_mgmt=WPA-EAP-SUITE-B-192 +{% else %} +wpa_key_mgmt=WPA-EAP WPA-EAP-SHA256 +{% endif %} {% if security.wpa.radius.server is defined %} # RADIUS client forced local IP address for the access point diff --git a/data/templates/wifi/wpa_supplicant.conf.tmpl b/data/templates/wifi/wpa_supplicant.conf.tmpl index f84892dc0..20b4f7976 100644 --- a/data/templates/wifi/wpa_supplicant.conf.tmpl +++ b/data/templates/wifi/wpa_supplicant.conf.tmpl @@ -4,10 +4,78 @@ # https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf network={ + # ssid: SSID (mandatory); network name in one of the optional formats: + # - an ASCII string with double quotation + # - a hex string (two characters per octet of SSID) + # - a printf-escaped ASCII string P"<escaped string>" + # ssid="{{ ssid }}" + + # scan_ssid: + # 0 = do not scan this SSID with specific Probe Request frames (default) + # 1 = scan with SSID-specific Probe Request frames (this can be used to + # find APs that do not accept broadcast SSID or use multiple SSIDs; + # this will add latency to scanning, so enable this only when needed) scan_ssid=1 + {% if security is defined and security.wpa is defined and security.wpa.passphrase is defined %} - key_mgmt=WPA-PSK + # ieee80211w: whether management frame protection is enabled + # 0 = disabled (default unless changed with the global pmf parameter) + # 1 = optional + # 2 = required + # The most common configuration options for this based on the PMF (protected + # management frames) certification program are: + # PMF enabled: ieee80211w=1 and key_mgmt=WPA-EAP WPA-EAP-SHA256 + # PMF required: ieee80211w=2 and key_mgmt=WPA-EAP-SHA256 + # (and similarly for WPA-PSK and WPA-PSK-SHA256 if WPA2-Personal is used) + # WPA3-Personal-only mode: ieee80211w=2 and key_mgmt=SAE + ieee80211w=1 + + # key_mgmt: list of accepted authenticated key management protocols + # WPA-PSK = WPA pre-shared key (this requires 'psk' field) + # WPA-EAP = WPA using EAP authentication + # IEEE8021X = IEEE 802.1X using EAP authentication and (optionally) dynamically + # generated WEP keys + # NONE = WPA is not used; plaintext or static WEP could be used + # WPA-NONE = WPA-None for IBSS (deprecated; use proto=RSN key_mgmt=WPA-PSK + # instead) + # FT-PSK = Fast BSS Transition (IEEE 802.11r) with pre-shared key + # FT-EAP = Fast BSS Transition (IEEE 802.11r) with EAP authentication + # FT-EAP-SHA384 = Fast BSS Transition (IEEE 802.11r) with EAP authentication + # and using SHA384 + # WPA-PSK-SHA256 = Like WPA-PSK but using stronger SHA256-based algorithms + # WPA-EAP-SHA256 = Like WPA-EAP but using stronger SHA256-based algorithms + # SAE = Simultaneous authentication of equals; pre-shared key/password -based + # authentication with stronger security than WPA-PSK especially when using + # not that strong password; a.k.a. WPA3-Personal + # FT-SAE = SAE with FT + # WPA-EAP-SUITE-B = Suite B 128-bit level + # WPA-EAP-SUITE-B-192 = Suite B 192-bit level + # OSEN = Hotspot 2.0 Rel 2 online signup connection + # FILS-SHA256 = Fast Initial Link Setup with SHA256 + # FILS-SHA384 = Fast Initial Link Setup with SHA384 + # FT-FILS-SHA256 = FT and Fast Initial Link Setup with SHA256 + # FT-FILS-SHA384 = FT and Fast Initial Link Setup with SHA384 + # OWE = Opportunistic Wireless Encryption (a.k.a. Enhanced Open) + # DPP = Device Provisioning Protocol + # If not set, this defaults to: WPA-PSK WPA-EAP +{% if security.wpa.mode is defined and security.wpa.mode == 'wpa3' %} + key_mgmt=SAE +{% else %} + key_mgmt=WPA-PSK WPA-PSK-SHA256 +{% endif %} + + # psk: WPA preshared key; 256-bit pre-shared key + # The key used in WPA-PSK mode can be entered either as 64 hex-digits, i.e., + # 32 bytes or as an ASCII passphrase (in which case, the real PSK will be + # generated using the passphrase and SSID). ASCII passphrase must be between + # 8 and 63 characters (inclusive). ext:<name of external PSK field> format can + # be used to indicate that the PSK/passphrase is stored in external storage. + # This field is not needed, if WPA-EAP is used. + # Note: Separate tool, wpa_passphrase, can be used to generate 256-bit keys + # from ASCII passphrase. This process uses lot of CPU and wpa_supplicant + # startup and reconfiguration time can be optimized by generating the PSK only + # only when the passphrase or SSID has actually changed. psk="{{ security.wpa.passphrase }}" {% else %} key_mgmt=NONE diff --git a/data/templates/wwan/chat.tmpl b/data/templates/wwan/chat.tmpl index a3395c057..386af37e6 100644 --- a/data/templates/wwan/chat.tmpl +++ b/data/templates/wwan/chat.tmpl @@ -1,6 +1,10 @@ ABORT 'NO DIAL TONE' ABORT 'NO ANSWER' ABORT 'NO CARRIER' ABORT DELAYED '' AT OK ATZ +{% if ipv6 is defined and ipv6.address is defined and ipv6.address.autoconf is defined %} +OK 'AT+CGDCONT=1,"IPV4V6","{{ apn }}"' +{% else %} OK 'AT+CGDCONT=1,"IP","{{ apn }}"' +{% endif %} OK ATD*99# CONNECT '' diff --git a/data/templates/wwan/peer.tmpl b/data/templates/wwan/peer.tmpl index e23881bf8..2807a79a4 100644 --- a/data/templates/wwan/peer.tmpl +++ b/data/templates/wwan/peer.tmpl @@ -4,15 +4,19 @@ ifname {{ ifname }} ipparam {{ ifname }} linkname {{ ifname }} + {{ "usepeerdns" if no_peer_dns is defined }} # physical device {{ device }} lcp-echo-failure 0 115200 debug -debug mtu {{ mtu }} mru {{ mtu }} +{% if ipv6 is defined and ipv6.address is defined and ipv6.address.autoconf is defined %} ++ipv6 +ipv6cp-use-ipaddr +{% endif %} nodefaultroute ipcp-max-failure 4 ipcp-accept-local diff --git a/debian/control b/debian/control index ebcfc6c43..c5dda7883 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, @@ -104,6 +106,7 @@ Depends: telnet, tftpd-hpa, traceroute, + tuned, udp-broadcast-relay, usb-modeswitch, usbutils, 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 <n> 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/dhcp-options.xml.i b/interface-definitions/include/dhcp-options.xml.i index e33022361..df9c7a97a 100644 --- a/interface-definitions/include/dhcp-options.xml.i +++ b/interface-definitions/include/dhcp-options.xml.i @@ -6,17 +6,23 @@ <children> <leafNode name="client-id"> <properties> - <help>DHCP client identifier</help> + <help>Identifier used by client to identify itself to the DHCP server</help> </properties> </leafNode> <leafNode name="host-name"> <properties> - <help>DHCP client host name (overrides system host name)</help> + <help>Override system host-name sent to DHCP server</help> </properties> </leafNode> <leafNode name="vendor-class-id"> <properties> - <help>DHCP client vendor type</help> + <help>Identify the vendor client type to the DHCP server</help> + </properties> + </leafNode> + <leafNode name="no-default-route"> + <properties> + <help>Do not request routers from DHCP server</help> + <valueless/> </properties> </leafNode> </children> diff --git a/interface-definitions/include/interface-ipv4.xml.i b/interface-definitions/include/interface-ipv4-options.xml.i index 66842ab9b..416e1adf5 100644 --- a/interface-definitions/include/interface-ipv4.xml.i +++ b/interface-definitions/include/interface-ipv4-options.xml.i @@ -1,14 +1,17 @@ -<!-- included start from interface-ipv4.xml.i --> +<!-- included start from interface-ipv4-options.xml.i --> <node name="ip"> <properties> <help>IPv4 routing parameters</help> </properties> <children> + #include <include/interface-arp-cache-timeout.xml.i> #include <include/interface-disable-arp-filter.xml.i> #include <include/interface-disable-forwarding.xml.i> #include <include/interface-enable-arp-accept.xml.i> #include <include/interface-enable-arp-announce.xml.i> #include <include/interface-enable-arp-ignore.xml.i> + #include <include/interface-enable-proxy-arp.xml.i> + #include <include/interface-proxy-arp-pvlan.xml.i> </children> </node> <!-- included end --> diff --git a/interface-definitions/include/interface-ipv6.xml.i b/interface-definitions/include/interface-ipv6-options.xml.i index c9299890b..a94c6572b 100644 --- a/interface-definitions/include/interface-ipv6.xml.i +++ b/interface-definitions/include/interface-ipv6-options.xml.i @@ -1,4 +1,4 @@ -<!-- included start from interface-ipv6.xml.i --> +<!-- included start from interface-ipv6-options.xml.i --> <node name="ipv6"> <properties> <help>IPv6 routing parameters</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..66fc5f7a9 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -80,25 +80,8 @@ </properties> <defaultValue>layer2</defaultValue> </leafNode> - <node name="ip"> - <children> - #include <include/interface-arp-cache-timeout.xml.i> - #include <include/interface-disable-arp-filter.xml.i> - #include <include/interface-disable-forwarding.xml.i> - #include <include/interface-enable-arp-accept.xml.i> - #include <include/interface-enable-arp-announce.xml.i> - #include <include/interface-enable-arp-ignore.xml.i> - #include <include/interface-enable-proxy-arp.xml.i> - #include <include/interface-proxy-arp-pvlan.xml.i> - </children> - </node> - <node name="ipv6"> - <children> - #include <include/ipv6-address.xml.i> - #include <include/ipv6-disable-forwarding.xml.i> - #include <include/ipv6-dup-addr-detect-transmits.xml.i> - </children> - </node> + #include <include/interface-ipv4-options.xml.i> + #include <include/interface-ipv6-options.xml.i> #include <include/interface-mac.xml.i> <leafNode name="min-links"> <properties> @@ -170,7 +153,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-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 0a777865b..778acda78 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -40,6 +40,7 @@ #include <include/interface-disable-link-detect.xml.i> #include <include/interface-disable.xml.i> #include <include/interface-vrf.xml.i> + #include <include/interface-mtu-68-16000.xml.i> <leafNode name="forwarding-delay"> <properties> <help>Forwarding delay</help> @@ -81,23 +82,8 @@ </leafNode> </children> </node> - <node name="ip"> - <children> - #include <include/interface-arp-cache-timeout.xml.i> - #include <include/interface-enable-arp-accept.xml.i> - #include <include/interface-disable-forwarding.xml.i> - #include <include/interface-enable-arp-announce.xml.i> - #include <include/interface-enable-arp-ignore.xml.i> - #include <include/interface-disable-arp-filter.xml.i> - </children> - </node> - <node name="ipv6"> - <children> - #include <include/ipv6-address.xml.i> - #include <include/ipv6-disable-forwarding.xml.i> - #include <include/ipv6-dup-addr-detect-transmits.xml.i> - </children> - </node> + #include <include/interface-ipv4-options.xml.i> + #include <include/interface-ipv6-options.xml.i> #include <include/interface-mac.xml.i> <leafNode name="max-age"> <properties> @@ -126,6 +112,37 @@ </completionHelp> </properties> <children> + <leafNode name="native-vlan"> + <properties> + <help>Specify VLAN id which should natively be present on the link</help> + <valueHelp> + <format>1-4094</format> + <description>Virtual Local Area Network (VLAN) ID</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-4094"/> + </constraint> + <constraintErrorMessage>VLAN ID must be between 1 and 4094</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="allowed-vlan"> + <properties> + <help>Specify VLAN id which is allowed in this trunk interface</help> + <valueHelp> + <format><id></format> + <description>VLAN id allowed to pass this interface</description> + </valueHelp> + <valueHelp> + <format><idN>-<idM></format> + <description>VLAN id range allowed on this interface (use '-' as delimiter)</description> + </valueHelp> + <constraint> + <regex>^([0-9]{1,4}-[0-9]{1,4})|([0-9]{1,4})$</regex> + </constraint> + <constraintErrorMessage>not a valid VLAN ID value or range</constraintErrorMessage> + <multi/> + </properties> + </leafNode> <leafNode name="cost"> <properties> <help>Bridge port cost</help> @@ -178,6 +195,8 @@ <valueless/> </properties> </leafNode> + #include <include/vif-s.xml.i> + #include <include/vif.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index a19a766d3..83f3d9e46 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -14,10 +14,6 @@ <format>ethN</format> <description>Ethernet interface name</description> </valueHelp> - <valueHelp> - <format>en[ospx]N</format> - <description>Ethernet interface name</description> - </valueHelp> </properties> <children> #include <include/address-ipv4-ipv6-dhcp.xml.i> @@ -59,27 +55,10 @@ <defaultValue>auto</defaultValue> </leafNode> #include <include/interface-hw-id.xml.i> - <node name="ip"> - <children> - #include <include/interface-arp-cache-timeout.xml.i> - #include <include/interface-disable-arp-filter.xml.i> - #include <include/interface-disable-forwarding.xml.i> - #include <include/interface-enable-arp-accept.xml.i> - #include <include/interface-enable-arp-announce.xml.i> - #include <include/interface-enable-arp-ignore.xml.i> - #include <include/interface-enable-proxy-arp.xml.i> - #include <include/interface-proxy-arp-pvlan.xml.i> - </children> - </node> - <node name="ipv6"> - <children> - #include <include/ipv6-address.xml.i> - #include <include/ipv6-disable-forwarding.xml.i> - #include <include/ipv6-dup-addr-detect-transmits.xml.i> - </children> - </node> + #include <include/interface-ipv4-options.xml.i> + #include <include/interface-ipv6-options.xml.i> #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..0c776e3c3 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -19,23 +19,10 @@ #include <include/address-ipv4-ipv6.xml.i> #include <include/interface-description.xml.i> #include <include/interface-disable.xml.i> - <node name="ip"> - <properties> - <help>IPv4 routing parameters</help> - </properties> - <children> - #include <include/interface-arp-cache-timeout.xml.i> - #include <include/interface-enable-proxy-arp.xml.i> - </children> - </node> - <node name="ipv6"> - <children> - #include <include/ipv6-disable-forwarding.xml.i> - #include <include/ipv6-dup-addr-detect-transmits.xml.i> - </children> - </node> + #include <include/interface-ipv4-options.xml.i> + #include <include/interface-ipv6-options.xml.i> #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..a8ddb74fb 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -53,13 +53,8 @@ </properties> <defaultValue>udp</defaultValue> </leafNode> - <node name="ipv6"> - <children> - #include <include/ipv6-address.xml.i> - #include <include/ipv6-disable-forwarding.xml.i> - #include <include/ipv6-dup-addr-detect-transmits.xml.i> - </children> - </node> + #include <include/interface-ipv4-options.xml.i> + #include <include/interface-ipv6-options.xml.i> <leafNode name="local-ip"> <properties> <help>Local IP address for L2TPv3 tunnel</help> @@ -76,7 +71,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..4d2581906 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -17,6 +17,8 @@ </properties> <children> #include <include/address-ipv4-ipv6.xml.i> + #include <include/interface-ipv4-options.xml.i> + #include <include/interface-ipv6-options.xml.i> <node name="security"> <properties> <help>Security/Encryption Settings</help> @@ -107,7 +109,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..32ba5ea01 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -23,25 +23,8 @@ #include <include/interface-disable-link-detect.xml.i> #include <include/interface-disable.xml.i> #include <include/interface-vrf.xml.i> - <node name="ip"> - <children> - #include <include/interface-arp-cache-timeout.xml.i> - #include <include/interface-disable-arp-filter.xml.i> - #include <include/interface-disable-forwarding.xml.i> - #include <include/interface-enable-arp-accept.xml.i> - #include <include/interface-enable-arp-announce.xml.i> - #include <include/interface-enable-arp-ignore.xml.i> - #include <include/interface-enable-proxy-arp.xml.i> - #include <include/interface-proxy-arp-pvlan.xml.i> - </children> - </node> - <node name="ipv6"> - <children> - #include <include/ipv6-address.xml.i> - #include <include/ipv6-disable-forwarding.xml.i> - #include <include/ipv6-dup-addr-detect-transmits.xml.i> - </children> - </node> + #include <include/interface-ipv4-options.xml.i> + #include <include/interface-ipv6-options.xml.i> #include <include/source-interface-ethernet.xml.i> #include <include/interface-mac.xml.i> <leafNode name="mode"> @@ -73,7 +56,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-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index 64520ce99..c3f178d59 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -22,8 +22,8 @@ #include <include/interface-disable-link-detect.xml.i> #include <include/interface-vrf.xml.i> #include <include/interface-mtu-64-8024.xml.i> - #include <include/interface-ipv4.xml.i> - #include <include/interface-ipv6.xml.i> + #include <include/interface-ipv4-options.xml.i> + #include <include/interface-ipv6-options.xml.i> <leafNode name="local-ip"> <properties> <help>Local IP address for this tunnel</help> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 7fdead16a..c7725fed3 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -35,24 +35,8 @@ </constraint> </properties> </leafNode> - <node name="ip"> - <children> - #include <include/interface-arp-cache-timeout.xml.i> - #include <include/interface-disable-arp-filter.xml.i> - #include <include/interface-disable-forwarding.xml.i> - #include <include/interface-enable-arp-accept.xml.i> - #include <include/interface-enable-arp-announce.xml.i> - #include <include/interface-enable-arp-ignore.xml.i> - #include <include/interface-enable-proxy-arp.xml.i> - </children> - </node> - <node name="ipv6"> - <children> - #include <include/ipv6-address.xml.i> - #include <include/ipv6-disable-forwarding.xml.i> - #include <include/ipv6-dup-addr-detect-transmits.xml.i> - </children> - </node> + #include <include/interface-ipv4-options.xml.i> + #include <include/interface-ipv6-options.xml.i> <leafNode name="source-address"> <properties> <help>VXLAN source address</help> @@ -67,7 +51,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-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 423ec7ba2..fdea1e3ab 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -5,7 +5,7 @@ <tagNode name="wireless" owner="${vyos_conf_scripts_dir}/interfaces-wireless.py"> <properties> <help>Wireless (WiFi/WLAN) Network Interface</help> - <priority>400</priority> + <priority>318</priority> <constraint> <regex>^wlan[0-9]+$</regex> </constraint> @@ -58,7 +58,7 @@ <description>Supported channel set width both 20 MHz and 40 MHz with secondary channel below primary channel</description> </valueHelp> <constraint> - <regex>(ht20|ht40\+|ht40-)</regex> + <regex>^(ht20|ht40\+|ht40-)$</regex> </constraint> <multi/> </properties> @@ -108,7 +108,7 @@ <description>Set maximum A-MSDU length to 7935 octets</description> </valueHelp> <constraint> - <regex>(3839|7935)</regex> + <regex>^(3839|7935)$</regex> </constraint> </properties> </leafNode> @@ -127,7 +127,7 @@ <description>Short GI for 40 MHz</description> </valueHelp> <constraint> - <regex>(20|40)</regex> + <regex>^(20|40)$</regex> </constraint> <multi/> </properties> @@ -147,7 +147,7 @@ <description>DYNAMIC Spatial Multiplexing (SM) Power Save</description> </valueHelp> <constraint> - <regex>(static|dynamic)</regex> + <regex>^(static|dynamic)$</regex> </constraint> </properties> </leafNode> @@ -164,7 +164,7 @@ <description>Number of spacial streams that can use RX STBC</description> </valueHelp> <constraint> - <regex>[1-3]+</regex> + <regex>^[1-3]+$</regex> </constraint> <constraintErrorMessage>Invalid capability item</constraintErrorMessage> </properties> @@ -243,7 +243,7 @@ <description>Support for operation as multi user beamformee</description> </valueHelp> <constraint> - <regex>(single-user-beamformer|single-user-beamformee|multi-user-beamformer|multi-user-beamformee)</regex> + <regex>^(single-user-beamformer|single-user-beamformee|multi-user-beamformer|multi-user-beamformee)$</regex> </constraint> <multi/> </properties> @@ -329,7 +329,7 @@ <description>Station can provide VHT MFB in response to VHT MRQ and unsolicited VHT MFB</description> </valueHelp> <constraint> - <regex>(unsolicited|both)</regex> + <regex>^(unsolicited|both)$</regex> </constraint> <constraintErrorMessage>Invalid capability item</constraintErrorMessage> </properties> @@ -361,7 +361,7 @@ <description>ncrease Maximum MPDU length to 11454 octets</description> </valueHelp> <constraint> - <regex>(7991|11454)</regex> + <regex>^(7991|11454)$</regex> </constraint> </properties> </leafNode> @@ -380,7 +380,7 @@ <description>Short GI for 160 MHz</description> </valueHelp> <constraint> - <regex>(80|160)</regex> + <regex>^(80|160)$</regex> </constraint> <multi/> </properties> @@ -398,7 +398,7 @@ <description>Number of spacial streams that can use RX STBC</description> </valueHelp> <constraint> - <regex>[1-4]+</regex> + <regex>^[1-4]+$</regex> </constraint> <constraintErrorMessage>Invalid capability item</constraintErrorMessage> </properties> @@ -443,6 +443,22 @@ </constraint> </properties> </leafNode> + <leafNode name="country-code"> + <properties> + <help>Indicate country in which device is operating</help> + <completionHelp> + <list>US EU JP DE UK CN ES FR RU</list> + </completionHelp> + <valueHelp> + <format><code%gt;</format> + <description>ISO/IEC 3166-1 Country Code</description> + </valueHelp> + <constraint> + <regex>^[A-Z][A-Z]$</regex> + </constraint> + <constraintErrorMessage>Invalid ISO/IEC 3166-1 Country Code</constraintErrorMessage> + </properties> + </leafNode> #include <include/interface-description.xml.i> #include <include/dhcp-options.xml.i> #include <include/dhcpv6-options.xml.i> @@ -461,25 +477,8 @@ <valueless/> </properties> </leafNode> - <node name="ip"> - <children> - #include <include/interface-arp-cache-timeout.xml.i> - #include <include/interface-disable-arp-filter.xml.i> - #include <include/interface-disable-forwarding.xml.i> - #include <include/interface-enable-arp-accept.xml.i> - #include <include/interface-enable-arp-announce.xml.i> - #include <include/interface-enable-arp-ignore.xml.i> - #include <include/interface-enable-proxy-arp.xml.i> - #include <include/interface-proxy-arp-pvlan.xml.i> - </children> - </node> - <node name="ipv6"> - <children> - #include <include/ipv6-address.xml.i> - #include <include/ipv6-disable-forwarding.xml.i> - #include <include/ipv6-dup-addr-detect-transmits.xml.i> - </children> - </node> + #include <include/interface-ipv4-options.xml.i> + #include <include/interface-ipv6-options.xml.i> #include <include/interface-hw-id.xml.i> <leafNode name="isolate-stations"> <properties> @@ -520,7 +519,7 @@ <description>MFP enforced</description> </valueHelp> <constraint> - <regex>(disabled|optional|required)</regex> + <regex>^(disabled|optional|required)$</regex> </constraint> </properties> </leafNode> @@ -681,7 +680,7 @@ <properties> <help>WPA mode</help> <completionHelp> - <list>wpa wpa2 both</list> + <list>wpa wpa2 wpa+wpa2 wpa3</list> </completionHelp> <valueHelp> <format>wpa</format> @@ -692,15 +691,15 @@ <description>WPA2 (full IEEE 802.11i/RSN)</description> </valueHelp> <valueHelp> - <format>both</format> + <format>wpa+wpa2</format> <description>Allow both WPA and WPA2</description> </valueHelp> <constraint> - <regex>^(wpa|wpa2|both)$</regex> + <regex>^(wpa|wpa2|wpa\+wpa2|wpa3)$</regex> </constraint> <constraintErrorMessage>Unknown WPA mode</constraintErrorMessage> </properties> - <defaultValue>both</defaultValue> + <defaultValue>wpa+wpa2</defaultValue> </leafNode> <leafNode name="passphrase"> <properties> @@ -782,25 +781,4 @@ </tagNode> </children> </node> - <node name="system"> - <children> - <leafNode name="wifi-regulatory-domain" owner="${vyos_conf_scripts_dir}/system-wifi-regdom.py"> - <properties> - <help>Wireless regulatory domain (mandatory)</help> - <priority>305</priority> - <completionHelp> - <list>US EU JP DE UK CN</list> - </completionHelp> - <valueHelp> - <format><code%gt;</format> - <description>Country code (ISO/IEC 3166-1)</description> - </valueHelp> - <constraint> - <regex>[A-Z][A-Z]$</regex> - </constraint> - <constraintErrorMessage>invalid country code</constraintErrorMessage> - </properties> - </leafNode> - </children> - </node> </interfaceDefinition> 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><interface></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><interface></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..4df2be4e7 100644 --- a/interface-definitions/protocols-mpls.xml.in +++ b/interface-definitions/protocols-mpls.xml.in @@ -11,15 +11,15 @@ <children> <node name="ldp"> <properties> - <help>LDP options</help> + <help>Label Distribution Protocol (LDP)</help> </properties> <children> <leafNode name="router-id"> <properties> - <help>x.x.x.x Label Switch Router (LSR) id</help> + <help>Label Distribution Protocol (LDP) router ID</help> <valueHelp> <format>ipv4</format> - <description>LSR ipv4 id</description> + <description>LDP IPv4 ID</description> </valueHelp> <constraint> <validator name="ipv4-address"/> @@ -28,10 +28,10 @@ </leafNode> <tagNode name="neighbor"> <properties> - <help>LDP Id of neighbor</help> + <help>LDP neighbor parameters</help> <valueHelp> <format>ipv4</format> - <description>neighbor IPv4 id</description> + <description>Neighbor IPv4 address</description> </valueHelp> <constraint> <validator name="ipv4-address"/> @@ -40,7 +40,35 @@ <children> <leafNode name="password"> <properties> - <help>Peer password</help> + <help>Neighbor password</help> + </properties> + </leafNode> + <leafNode name="ttl-security"> + <properties> + <help>Neighbor TTL security</help> + <completionHelp> + <list>disable</list> + </completionHelp> + <valueHelp> + <format><1-254></format> + <description>TTL</description> + </valueHelp> + <valueHelp> + <format>disable</format> + <description>Disable neighbor TTL security</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="session-holdtime"> + <properties> + <help>Session IPv4 hold time</help> + <valueHelp> + <format>15-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 15-65535"/> + </constraint> </properties> </leafNode> </children> @@ -54,9 +82,21 @@ </valueHelp> </properties> <children> - <leafNode name="hello-holdtime"> + <leafNode name="hello-ipv4-holdtime"> + <properties> + <help>Hello IPv4 hold time</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-ipv4-interval"> <properties> - <help>Hello holdtime</help> + <help>Hello IPv4 interval</help> <valueHelp> <format>1-65535</format> <description>Time in seconds</description> @@ -66,9 +106,21 @@ </constraint> </properties> </leafNode> - <leafNode name="hello-interval"> + <leafNode name="hello-ipv6-holdtime"> <properties> - <help>Hello interval</help> + <help>Hello IPv6 hold time</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> @@ -80,7 +132,7 @@ </leafNode> <leafNode name="session-ipv4-holdtime"> <properties> - <help>Session ipv4 holdtime</help> + <help>Session IPv4 hold time</help> <valueHelp> <format>15-65535</format> <description>Time in seconds</description> @@ -92,7 +144,7 @@ </leafNode> <leafNode name="session-ipv6-holdtime"> <properties> - <help>Session ipv6 holdtime</help> + <help>Session IPv6 hold time</help> <valueHelp> <format>15-65535</format> <description>Time in seconds</description> @@ -104,7 +156,7 @@ </leafNode> <leafNode name="transport-ipv4-address"> <properties> - <help>Transport ipv4 address</help> + <help>Transport IPv4 address</help> <valueHelp> <format>ipv4</format> <description>IPv4 bind as transport</description> @@ -116,7 +168,7 @@ </leafNode> <leafNode name="transport-ipv6-address"> <properties> - <help>Transport ipv6 address</help> + <help>Transport IPv6 address</help> <valueHelp> <format>ipv6</format> <description>IPv6 bind as transport</description> @@ -128,6 +180,132 @@ </leafNode> </children> </node> + <node name="targeted-neighbor"> + <properties> + <help>Targeted LDP neighbor/session parameters</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>Targeted IPv4 neighbor/session parameters</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Neighbor/session address</help> + <valueHelp> + <format>ipv4</format> + <description>Neighbor/session address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="enable"> + <properties> + <help>Accept and respond to targeted hellos</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="hello-interval"> + <properties> + <help>Hello 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-holdtime"> + <properties> + <help>Hello hold time</help> + <valueHelp> + <format>1-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>Targeted IPv6 neighbor/session parameters</help> + </properties> + <children> + <leafNode name="address"> + <properties> + <help>Neighbor/session address</help> + <valueHelp> + <format>ipv6</format> + <description>Neighbor/session address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="enable"> + <properties> + <help>Accept and respond to targeted hellos</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="hello-interval"> + <properties> + <help>Hello 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-holdtime"> + <properties> + <help>Hello hold time</help> + <valueHelp> + <format>1-65535</format> + <description>Time in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="parameters"> + <properties> + <help>Label Distribution Protocol (LDP) miscellaneous parameters</help> + </properties> + <children> + <leafNode name="cisco-interop-tlv"> + <properties> + <help>Enable Cisco non-compliant format capability TLV</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="transport-prefer-ipv4"> + <properties> + <help>Prefer IPv4 for TCP peer transport connection</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> <node name="export"> <properties> <help>Export parameters</help> @@ -163,7 +341,7 @@ </node> <leafNode name="interface"> <properties> - <help>Listen interface for LDP</help> + <help>Enable LDP and neighbor discovery on interface</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> @@ -172,6 +350,31 @@ </leafNode> </children> </node> + <node name="parameters"> + <properties> + <help>Multiprotocol Label Switching (MPLS) miscellaneous parameters</help> + </properties> + <children> + <leafNode name="no-propagate-ttl"> + <properties> + <help>Disable copy of IP TTL to MPLS TTL</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="maximum-ttl"> + <properties> + <help>Maximum TTL for MPLS packets</help> + <valueHelp> + <format>1-255</format> + <description>Maximum hops allowed</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + </leafNode> + </children> + </node> </children> </node> </children> 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/system-options.xml.in b/interface-definitions/system-options.xml.in index a5fec10db..297f5891e 100644 --- a/interface-definitions/system-options.xml.in +++ b/interface-definitions/system-options.xml.in @@ -38,11 +38,24 @@ <constraintErrorMessage>Must be ignore, reboot, or poweroff</constraintErrorMessage> </properties> </leafNode> - <leafNode name="reboot-on-panic"> - <properties> - <help>Reboot system on kernel panic</help> - <valueless/> - </properties> + <leafNode name="performance"> + <properties> + <help>Tune system performance</help> + <completionHelp> + <list>throughput latency</list> + </completionHelp> + <valueHelp> + <format>throughput</format> + <description>Tune for maximum network throughput</description> + </valueHelp> + <valueHelp> + <format>latency</format> + <description>Tune for low network latency</description> + </valueHelp> + <constraint> + <regex>^(throughput|latency)$</regex> + </constraint> + </properties> </leafNode> <node name="http-client"> <properties> @@ -53,6 +66,12 @@ #include <include/source-address-ipv4-ipv6.xml.i> </children> </node> + <leafNode name="reboot-on-panic"> + <properties> + <help>Reboot system on kernel panic</help> + <valueless/> + </properties> + </leafNode> <node name="ssh-client"> <properties> <help>Global options used for SSH client</help> 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 <n> seconds on failure</help> diff --git a/op-mode-definitions/show-interfaces-bridge.xml b/op-mode-definitions/show-interfaces-bridge.xml index 85fde95b5..cc4b248b6 100644 --- a/op-mode-definitions/show-interfaces-bridge.xml +++ b/op-mode-definitions/show-interfaces-bridge.xml @@ -33,6 +33,12 @@ </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=bridge --action=show</command> </leafNode> + <leafNode name="vlan"> + <properties> + <help>View the VLAN filter settings of the bridge</help> + </properties> + <command>/usr/sbin/bridge -c vlan show</command> + </leafNode> </children> </node> </children> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 62df3334c..b14f96364 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]) @@ -219,6 +219,28 @@ def is_member(conf, interface, intftype=None): old_level = conf.set_level(old_level) return ret_val +def has_vlan_subinterface_configured(conf, intf): + """ + Checks if interface has an VLAN subinterface configured. + Checks the following config nodes: + 'vif', 'vif-s' + + Returns True if interface has VLAN subinterface configured, False if it doesn't. + """ + from vyos.ifconfig import Section + ret = False + + old_level = conf.get_level() + conf.set_level([]) + + intfpath = 'interfaces ' + Section.get_config_path(intf) + if ( conf.exists(f'{intfpath} vif') or + conf.exists(f'{intfpath} vif-s')): + ret = True + + conf.set_level(old_level) + return ret + def is_source_interface(conf, interface, intftype=None): """ Checks if passed interface is configured as source-interface of other @@ -332,7 +354,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: @@ -409,7 +431,7 @@ def get_accel_dict(config, base, chap_secrets): Return a dictionary with the necessary interface config keys. """ from vyos.util import get_half_cpus - from vyos.validate import is_ipv4 + from vyos.template import is_ipv4 dict = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) @@ -419,12 +441,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 +470,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/configverify.py b/python/vyos/configverify.py index 422483663..2a5dc7af2 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): """ @@ -51,7 +51,7 @@ def verify_mtu_ipv6(config): recurring validation if the specified MTU can be used when IPv6 is configured on the interface. IPv6 requires a 1280 bytes MTU. """ - from vyos.validate import is_ipv6 + from vyos.template import is_ipv6 if 'mtu' in config: # IPv6 minimum required link mtu min_mtu = 1280 @@ -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..7eac9b886 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): @@ -41,6 +41,7 @@ class BridgeIf(Interface): 'section': 'bridge', 'prefixes': ['br', ], 'broadcast': True, + 'vlan': True, }, } @@ -73,6 +74,10 @@ class BridgeIf(Interface): 'validate': assert_boolean, 'location': '/sys/class/net/{ifname}/bridge/stp_state', }, + 'vlan_filter': { + 'validate': assert_boolean, + 'location': '/sys/class/net/{ifname}/bridge/vlan_filtering', + }, 'multicast_querier': { 'validate': assert_boolean, 'location': '/sys/class/net/{ifname}/bridge/multicast_querier', @@ -152,6 +157,16 @@ class BridgeIf(Interface): >>> BridgeIf('br0').set_stp(1) """ self.set_interface('stp', state) + + def set_vlan_filter(self, state): + """ + Set bridge Vlan Filter state. 0 -> Vlan Filter disabled, 1 -> Vlan Filter enabled + + Example: + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').set_vlan_filter(1) + """ + self.set_interface('vlan_filter', state) def set_multicast_querier(self, enable): """ @@ -177,8 +192,13 @@ class BridgeIf(Interface): >>> BridgeIf('br0').add_port('eth0') >>> BridgeIf('br0').add_port('eth1') """ + # Bridge port handling of wireless interfaces is done by hostapd. + if 'wlan' in interface: + return + return self.set_interface('add_port', interface) + def del_port(self, interface): """ Remove member port from bridge instance. @@ -197,6 +217,8 @@ class BridgeIf(Interface): # call base class first super().update(config) + + ifname = config['ifname'] # Set ageing time value = config.get('aging') @@ -223,17 +245,18 @@ 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) + vlan_filter = 0 - 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 @@ -260,7 +283,51 @@ class BridgeIf(Interface): if 'priority' in interface_config: value = interface_config.get('priority') lower.set_path_priority(value) - + + tmp = dict_search('native_vlan_removed', interface_config) + + if tmp and 'native_vlan_removed' not in interface_config: + vlan_id = tmp + cmd = f'bridge vlan add dev {interface} vid 1 pvid untagged master' + self._cmd(cmd) + cmd = f'bridge vlan del dev {interface} vid {vlan_id}' + self._cmd(cmd) + + tmp = dict_search('allowed_vlan_removed', interface_config) + + + for vlan_id in (tmp or []): + cmd = f'bridge vlan del dev {interface} vid {vlan_id}' + self._cmd(cmd) + + if 'native_vlan' in interface_config: + vlan_filter = 1 + cmd = f'bridge vlan del dev {interface} vid 1' + self._cmd(cmd) + vlan_id = interface_config['native_vlan'] + cmd = f'bridge vlan add dev {interface} vid {vlan_id} pvid untagged master' + self._cmd(cmd) + else: + cmd = f'bridge vlan del dev {interface} vid 1' + self._cmd(cmd) + + if 'allowed_vlan' in interface_config: + vlan_filter = 1 + for vlan in interface_config['allowed_vlan']: + cmd = f'bridge vlan add dev {interface} vid {vlan} master' + self._cmd(cmd) + + + vif = dict_search('vif', config) + if vif: + for vlan_id,vif_config in vif.items(): + cmd = f'bridge vlan add dev {ifname} vid {vlan_id} self master' + self._cmd(cmd) + + # enable/disable Vlan Filter + self.set_vlan_filter(vlan_filter) + + # Enable/Disable of an interface must always be done at the end of the # derived class to make use of the ref-counting set_admin_state() # function. We will only enable the interface if 'up' was called as diff --git a/python/vyos/ifconfig/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..893623284 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -34,9 +34,9 @@ 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.validate import is_ipv4 -from vyos.validate import is_ipv6 +from vyos.util import dict_search +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 from vyos.validate import is_intf_addr_assigned from vyos.validate import assert_boolean from vyos.validate import assert_list @@ -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/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py index 5fd90f9cf..8ed3d5afb 100644 --- a/python/vyos/ifconfig/l2tpv3.py +++ b/python/vyos/ifconfig/l2tpv3.py @@ -68,8 +68,9 @@ class L2TPv3If(Interface): cmd += ' peer_session_id {peer_session_id}' self._cmd(cmd.format(**self.config)) - # interface is always A/D down. It needs to be enabled explicitly - self.set_admin_state('down') + # No need for interface shut down. There exist no function to permanently enable tunnel. + # But you can disable interface permanently with shutdown/disable command. + self.set_admin_state('up') def remove(self): """ @@ -93,4 +94,24 @@ class L2TPv3If(Interface): if self.config['tunnel_id']: cmd = 'ip l2tp del tunnel tunnel_id {tunnel_id}' self._cmd(cmd.format(**self.config)) + + + 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/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/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index d8e89229d..da3bd4e89 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -24,7 +24,7 @@ from hurry.filesize import alternative from vyos.config import Config from vyos.ifconfig import Interface from vyos.ifconfig import Operational -from vyos.validate import is_ipv6 +from vyos.template import is_ipv6 class WireGuardOperational(Operational): def _dump(self): diff --git a/python/vyos/template.py b/python/vyos/template.py index c88ab04a0..58ba75972 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,95 @@ def render( # Custom template filters follow # ################################## - -@register_filter("address_from_cidr") -def vyos_address_from_cidr(text): +@register_filter('address_from_cidr') +def 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") -def vyos_netmask_from_cidr(text): - """ Take an IPv4/IPv6 CIDR prefix and convert the prefix length to a "subnet mask". +@register_filter('netmask_from_cidr') +def netmask_from_cidr(text): + """ 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('is_ip') +def is_ip(addr): + """ Check addr if it is an IPv4 or IPv6 address """ + return is_ipv4(addr) or is_ipv6(addr) + +@register_filter('is_ipv4') +def is_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 is_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 first_host_address(text): + """ Return first usable (host) 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:: + """ + 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 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 inc_ip(address, increment): + """ Increment given IP address by 'increment' + + Example (inc by 2): + - 10.0.0.0/24 -> 10.0.0.2 + - 2001:db8::/64 -> 2001:db8::2 + """ + from ipaddress import ip_interface + return str(ip_interface(address).ip + int(increment)) + +@register_filter('dec_ip') +def dec_ip(address, decrement): + """ Decrement given IP address by 'decrement' + + Example (inc by 2): + - 10.0.0.0/24 -> 10.0.0.2 + - 2001:db8::/64 -> 2001:db8::2 """ - return ip_network(text).netmask + from ipaddress import ip_interface + return str(ip_interface(address).ip - int(decrement)) 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..84a7bc2de 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,26 @@ 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 - """ - 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 - -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 - 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 + from vyos.template import is_ipv6 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 + from vyos.template import is_ipv4 # 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: @@ -96,6 +58,7 @@ def _is_intf_addr_assigned(intf, address, netmask=''): It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR address 192.0.2.1/24. """ + from vyos.template import is_ipv4 # check if the requested address type is configured at all # { @@ -149,10 +112,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 +127,9 @@ def is_subnet_connected(subnet, primary=False): Return True/False """ + from ipaddress import ip_address + from ipaddress import ip_network + from vyos.template import is_ipv6 # determine IP version (AF_INET or AF_INET6) depending on passed address addr_type = netifaces.AF_INET @@ -180,7 +145,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 +153,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 +189,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] @@ -265,7 +231,6 @@ def assert_mac(m): if octets[:5] == (0, 0, 94, 0, 1): raise ValueError(f'{m} is a VRRP MAC address') - def has_address_configured(conf, intf): """ Checks if interface has an address configured. 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..e46a16137 100644 --- a/smoketest/scripts/cli/base_accel_ppp_test.py +++ b/smoketest/scripts/cli/base_accel_ppp_test.py @@ -20,10 +20,10 @@ from configparser import ConfigParser from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError +from vyos.template import is_ipv4 from vyos.util import cmd from vyos.util import get_half_cpus from vyos.util import process_named_running -from vyos.validate import is_ipv4 class BasicAccelPPPTest: class BaseTest(unittest.TestCase): @@ -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..e02424073 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -22,8 +22,9 @@ 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.validate import is_intf_addr_assigned, is_ipv6_link_local +from vyos.util import dict_search +from vyos.validate import is_intf_addr_assigned +from vyos.validate import is_ipv6_link_local class BasicInterfaceTest: class BaseTest(unittest.TestCase): @@ -162,16 +163,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 +249,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_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index a1359680b..3b7f1bc9a 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -21,12 +21,16 @@ from base_interfaces_test import BasicInterfaceTest from glob import glob from netifaces import interfaces from vyos.ifconfig import Section +from vyos.util import cmd +import json class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() self._test_ipv6 = True + self._test_vlan = True + self._test_qinq = True self._base_path = ['interfaces', 'bridge'] self._interfaces = ['br0'] @@ -78,6 +82,94 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): self.session.delete(self._base_path + [interface, 'member']) self.session.commit() + + def test_vlan_filter(self): + """ Add member interface to bridge and set VLAN filter """ + for interface in self._interfaces: + base = self._base_path + [interface] + self.session.set(base + ['address', '192.0.2.1/24']) + self.session.set(base + ['vif', '2','address','192.0.3.1/24']) + + vlan_id = 101 + allowed_vlan = 2 + allowed_vlan_range = '4-9' + # assign members to bridge interface + for member in self._members: + base_member = base + ['member', 'interface', member] + self.session.set(base_member + ['allowed-vlan', str(allowed_vlan)]) + self.session.set(base_member + ['allowed-vlan', allowed_vlan_range]) + self.session.set(base_member + ['native-vlan', str(vlan_id)]) + vlan_id += 1 + + # commit config + self.session.commit() + + # Detect the vlan filter function + for interface in self._interfaces: + with open(f'/sys/class/net/{interface}/bridge/vlan_filtering', 'r') as f: + flags = f.read() + self.assertEqual(int(flags), 1) + + # Execute the program to obtain status information + + json_data = cmd('bridge -j vlan show', shell=True) + + vlan_filter_status = None + + vlan_filter_status = json.loads(json_data) + + + if vlan_filter_status is not None: + for interface_status in vlan_filter_status: + ifname = interface_status['ifname'] + for interface in self._members: + vlan_success = 0; + if interface == ifname: + vlans_status = interface_status['vlans'] + for vlan_status in vlans_status: + vlan_id = vlan_status['vlan'] + flag_num = 0 + if 'flags' in vlan_status: + flags = vlan_status['flags'] + for flag in flags: + flag_num = flag_num +1 + if vlan_id == 2: + if flag_num == 0: + vlan_success = vlan_success + 1 + else: + for id in range(4,10): + if vlan_id == id: + if flag_num == 0: + vlan_success = vlan_success + 1 + if vlan_id >= 101: + if flag_num == 2: + vlan_success = vlan_success + 1 + if vlan_success >= 7: + self.assertTrue(True) + else: + self.assertTrue(False) + + else: + self.assertTrue(False) + + + + + # check member interfaces are added on the bridge + + for interface in self._interfaces: + bridge_members = [] + for tmp in glob(f'/sys/class/net/{interface}/lower_*'): + bridge_members.append(os.path.basename(tmp).replace('lower_', '')) + + for member in self._members: + self.assertIn(member, bridge_members) + + # delete all members + for interface in self._interfaces: + self.session.delete(self._base_path + [interface, 'member']) + + self.session.commit() def test_vlan_members(self): """ T2945: ensure that VIFs are not dropped from bridge """ diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py index 5cc62e3e2..41e48c2f8 100755 --- a/smoketest/scripts/cli/test_interfaces_openvpn.py +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -26,6 +26,11 @@ 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 address_from_cidr +from vyos.template import dec_ip +from vyos.template import inc_ip +from vyos.template import last_host_address +from vyos.template import netmask_from_cidr PROCESS_NAME = 'openvpn' @@ -35,13 +40,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 +61,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 +197,215 @@ 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]) + + 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 = '' + client1_routes = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] + for ii in num_range: + interface = f'vtun{ii}' + subnet = f'192.0.{ii}.0/24' + client_ip = inc_ip(subnet, '5') + 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', 'subnet']) + + # clients + self.session.set(path + ['server', 'client', 'client1', 'ip', client_ip]) + for route in client1_routes: + self.session.set(path + ['server', 'client', 'client1', 'subnet', route]) + + 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 = inc_ip(subnet, '2') + stop_addr = last_host_address(subnet) + + client_ip = inc_ip(subnet, '5') + client_netmask = netmask_from_cidr(subnet) + + port = str(2000 + ii) + + config_file = f'/run/openvpn/{interface}.conf' + client_config_file = f'/run/openvpn/ccd/{interface}/client1' + 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) + + # Verify client + client_config = read_file(client_config_file) + + self.assertIn(f'ifconfig-push {client_ip} {client_netmask}', client_config) + for route in client1_routes: + self.assertIn('iroute {} {}'.format(address_from_cidr(route), netmask_from_cidr(route)), client_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() - def test_server_interfaces(self): - """ Create OpenVPN server interfaces using different client subnets. - Validate configuration afterwards. """ + 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 = '' @@ -142,6 +421,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', '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]) @@ -153,6 +434,8 @@ class TestInterfacesOpenVPN(unittest.TestCase): for ii in num_range: interface = f'vtun{ii}' subnet = f'192.0.{ii}.0/24' + start_addr = inc_ip(subnet, '4') + stop_addr = dec_ip(last_host_address(subnet), '1') port = str(2000 + ii) config_file = f'/run/openvpn/{interface}.conf' @@ -166,6 +449,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 +461,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 +475,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 +595,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_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index 7611ffe26..1033196ce 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -14,95 +14,289 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import unittest +import json from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.util import cmd from base_interfaces_test import BasicInterfaceTest -class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): - # encoding, tunnel endpoint (v4/v6), address (v4/v6) - _valid = [ - ('gre', 4, 4), - ('gre', 4, 6), - ('ip6gre', 6, 4), - ('ip6gre', 6, 6), - ('gre-bridge', 4, 4), - ('ipip', 4, 4), - ('ipip', 4, 6), - ('ipip6', 6, 4), - ('ipip6', 6, 6), - ('ip6ip6', 6, 6), - ('sit', 4, 6), - ] - - local = { - 4: '10.100.{}.1/24', - 6: '2001:db8:{}::1/64', - } - - remote = { - 4: '192.0.{}.1', - 6: '2002::{}:1', - } - - address = { - 4: '10.100.{}.1/24', - 6: '2001:db8:{}::1/64', - } +remote_ip4 = '192.0.2.100' +remote_ip6 = '2001:db8::ffff' +source_if = 'dum2222' +mtu = 1476 + +def tunnel_conf(interface): + tmp = cmd(f'ip -d -j link show {interface}') + # {'address': '2.2.2.2', + # 'broadcast': '192.0.2.10', + # 'flags': ['POINTOPOINT', 'NOARP', 'UP', 'LOWER_UP'], + # 'group': 'default', + # 'gso_max_segs': 65535, + # 'gso_max_size': 65536, + # 'ifindex': 10, + # 'ifname': 'tun10', + # 'inet6_addr_gen_mode': 'none', + # 'link': None, + # 'link_pointtopoint': True, + # 'link_type': 'gre', + # 'linkinfo': {'info_data': {'local': '2.2.2.2', + # 'pmtudisc': True, + # 'remote': '192.0.2.10', + # 'tos': '0x1', + # 'ttl': 255}, + # 'info_kind': 'gre'}, + # 'linkmode': 'DEFAULT', + # 'max_mtu': 65511, + # 'min_mtu': 68, + # 'mtu': 1476, + # 'num_rx_queues': 1, + # 'num_tx_queues': 1, + # 'operstate': 'UNKNOWN', + # 'promiscuity': 0, + # 'qdisc': 'noqueue', + # 'txqlen': 1000} + return json.loads(tmp)[0] +class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): - local = {} - remote = {} - address = {} + super().setUp() - self._intf_dummy = ['interfaces', 'dummy'] self._base_path = ['interfaces', 'tunnel'] - self._interfaces = ['tun{}'.format(n) for n in range(len(self._valid))] - self._test_mtu = True - super().setUp() - for number in range(len(self._valid)): - dum4 = 'dum4{}'.format(number) - dum6 = 'dum6{}'.format(number) + self.local_v4 = '192.0.2.1' + self.local_v6 = '2001:db8::1' + + self.session.set(['interfaces', 'dummy', source_if, 'address', self.local_v4 + '/32']) + self.session.set(['interfaces', 'dummy', source_if, 'address', self.local_v6 + '/128']) - ipv4 = self.local[4].format(number) - ipv6 = self.local[6].format(number) + self._options = { + 'tun10': ['encapsulation ipip', 'remote-ip 192.0.2.10', 'local-ip ' + self.local_v4], + 'tun20': ['encapsulation gre', 'remote-ip 192.0.2.20', 'local-ip ' + self.local_v4], + } - local.setdefault(4, {})[number] = ipv4 - local.setdefault(6, {})[number] = ipv6 + self._interfaces = list(self._options) - ipv4 = self.remote[4].format(number) - ipv6 = self.remote[6].format(number) + def tearDown(self): + self.session.delete(['interfaces', 'dummy', source_if]) + super().tearDown() - remote.setdefault(4, {})[number] = ipv4 - remote.setdefault(6, {})[number] = ipv6 + def test_ipip(self): + interface = 'tun100' + encapsulation = 'ipip' + local_if_addr = '10.10.10.1/24' - ipv4 = self.address[4].format(number) - ipv6 = self.address[6].format(number) + self.session.set(self._base_path + [interface, 'address', local_if_addr]) - address.setdefault(4, {})[number] = ipv4 - address.setdefault(6, {})[number] = ipv6 + # Must provide an "encapsulation" for tunnel tun10 + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) + + # Must configure either local-ip or dhcp-interface for tunnel ipip tun100 + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) + + # missing required option remote for ipip + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) + + # Configure Tunnel Source interface + self.session.set(self._base_path + [interface, 'source-interface', source_if]) - self.session.set(self._intf_dummy + [dum4, 'address', ipv4]) - self.session.set(self._intf_dummy + [dum6, 'address', ipv6]) self.session.commit() - for number, (encap, p2p, addr) in enumerate(self._valid): - intf = 'tun%d' % number - tunnel = {} - tunnel['encapsulation'] = encap - tunnel['local-ip'] = local[p2p][number].split('/')[0] - tunnel['remote-ip'] = remote[p2p][number].split('/')[0] - tunnel['address'] = address[addr][number] - for name in tunnel: - self.session.set(self._base_path + [intf, name, tunnel[name]]) + conf = tunnel_conf(interface) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['link_type']) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(source_if, conf['link']) - def tearDown(self): - self.session.delete(self._intf_dummy) - super().tearDown() + self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + + def test_ipip6(self): + interface = 'tun110' + encapsulation = 'ipip6' + local_if_addr = '10.10.10.1/24' + + self.session.set(self._base_path + [interface, 'address', local_if_addr]) + + # Must provide an "encapsulation" for tunnel tun10 + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) + + # Must configure either local-ip or dhcp-interface for tunnel ipip tun100 + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) + + # missing required option remote for ipip + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6]) + + # Configure Tunnel Source interface + self.session.set(self._base_path + [interface, 'source-interface', source_if]) + + self.session.commit() + + conf = tunnel_conf(interface) + self.assertEqual(interface, conf['ifname']) + self.assertEqual('tunnel6', conf['link_type']) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(source_if, conf['link']) + + self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) + + def test_ip6ip6(self): + interface = 'tun120' + encapsulation = 'ip6ip6' + local_if_addr = '2001:db8:f00::1/24' + + self.session.set(self._base_path + [interface, 'address', local_if_addr]) + + # Must provide an "encapsulation" for tunnel tun10 + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) + + # Must configure either local-ip or dhcp-interface for tunnel ipip tun100 + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) + + # missing required option remote for ipip + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6]) + + # Configure Tunnel Source interface + self.session.set(self._base_path + [interface, 'source-interface', source_if]) + + self.session.commit() + + conf = tunnel_conf(interface) + self.assertEqual(interface, conf['ifname']) + self.assertEqual('tunnel6', conf['link_type']) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(source_if, conf['link']) + + self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) + + def test_gre_ipv4(self): + interface = 'tun200' + encapsulation = 'gre' + local_if_addr = '172.16.1.1/24' + + self.session.set(self._base_path + [interface, 'address', local_if_addr]) + + # Must provide an "encapsulation" for tunnel tun10 + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) + + # Must configure either local-ip or dhcp-interface + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) + + # No assertion is raised for GRE remote-ip when missing + self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) + + # Configure Tunnel Source interface + self.session.set(self._base_path + [interface, 'source-interface', source_if]) + + self.session.commit() + + conf = tunnel_conf(interface) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['link_type']) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(source_if, conf['link']) + + self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + + + def test_gre_ipv6(self): + interface = 'tun210' + encapsulation = 'ip6gre' + local_if_addr = '2001:db8:f01::1/24' + + self.session.set(self._base_path + [interface, 'address', local_if_addr]) + + # Must provide an "encapsulation" for tunnel tun10 + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) + + # Must configure either local-ip or dhcp-interface + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) + + # No assertion is raised for GRE remote-ip when missing + self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6]) + + # Configure Tunnel Source interface + self.session.set(self._base_path + [interface, 'source-interface', source_if]) + + self.session.commit() + + conf = tunnel_conf(interface) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['link_type']) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(source_if, conf['link']) + + self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) + + + def test_sit(self): + interface = 'tun300' + encapsulation = 'sit' + local_if_addr = '172.16.2.1/24' + + self.session.set(self._base_path + [interface, 'address', local_if_addr]) + + # Must provide an "encapsulation" for tunnel tun10 + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) + + # Must configure either local-ip or dhcp-interface + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) + + # No assertion is raised for GRE remote-ip when missing + self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) + + # Source interface can not be used with si + self.session.set(self._base_path + [interface, 'source-interface', source_if]) + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.delete(self._base_path + [interface, 'source-interface']) + + self.session.commit() + + conf = tunnel_conf(interface) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['link_type']) + self.assertEqual(mtu, conf['mtu']) + + self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) if __name__ == '__main__': diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py index 0e93b6432..65cf127ce 100755 --- a/smoketest/scripts/cli/test_interfaces_wireless.py +++ b/smoketest/scripts/cli/test_interfaces_wireless.py @@ -18,14 +18,16 @@ import os import re import unittest +from vyos.configsession import ConfigSessionError from base_interfaces_test import BasicInterfaceTest + from vyos.util import process_named_running from vyos.util import check_kmod from vyos.util import read_file def get_config_value(interface, key): tmp = read_file(f'/run/hostapd/{interface}.conf') - tmp = re.findall(r'\n?{}=+(.*)'.format(key), tmp) + tmp = re.findall(f'{key}=+(.*)', tmp) return tmp[0] class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): @@ -36,15 +38,14 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): self._options = { 'wlan0': ['physical-device phy0', 'ssid VyOS-WIFI-0', 'type station', 'address 192.0.2.1/30'], - 'wlan1': ['physical-device phy0', 'ssid VyOS-WIFI-1', + 'wlan1': ['physical-device phy0', 'ssid VyOS-WIFI-1', 'country-code SE', 'type access-point', 'address 192.0.2.5/30', 'channel 0'], 'wlan10': ['physical-device phy1', 'ssid VyOS-WIFI-2', 'type station', 'address 192.0.2.9/30'], - 'wlan11': ['physical-device phy1', 'ssid VyOS-WIFI-3', + 'wlan11': ['physical-device phy1', 'ssid VyOS-WIFI-3', 'country-code SE', 'type access-point', 'address 192.0.2.13/30', 'channel 0'], } self._interfaces = list(self._options) - self.session.set(['system', 'wifi-regulatory-domain', 'SE']) def test_add_address_single(self): """ derived method to check if member interfaces are enslaved properly """ @@ -73,6 +74,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): self.session.set(self._base_path + [interface, 'ssid', ssid]) self.session.set(self._base_path + [interface, 'type', 'access-point']) self.session.set(self._base_path + [interface, 'channel', channel]) + self.session.set(self._base_path + [interface, 'country-code', 'SE']) # auto-powersave is special self.session.set(self._base_path + [interface, 'capabilities', 'ht', 'auto-powersave']) @@ -114,6 +116,8 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): # # Validate Config # + tmp = get_config_value(interface, 'interface') + self.assertEqual(interface, tmp) # ssid tmp = get_config_value(interface, 'ssid') @@ -138,6 +142,74 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): # Check for running process self.assertTrue(process_named_running('hostapd')) + def test_hostapd_wpa_config(self): + """ Check if hostapd config is properly generated """ + + # Only set the hostapd (access-point) options + interface = 'wlan0' + phy = 'phy0' + ssid = 'ssid' + channel = '0' + wpa_key = 'VyOSVyOSVyOS' + mode = 'n' + country = 'DE' + + self.session.set(self._base_path + [interface, 'physical-device', phy]) + self.session.set(self._base_path + [interface, 'type', 'access-point']) + self.session.set(self._base_path + [interface, 'mode', mode]) + + # SSID must be set + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'ssid', ssid]) + + # Channel must be set + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'channel', channel]) + + # Country-Code must be set + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'country-code', country]) + + self.session.set(self._base_path + [interface, 'security', 'wpa', 'mode', 'wpa2']) + self.session.set(self._base_path + [interface, 'security', 'wpa', 'passphrase', wpa_key]) + + self.session.commit() + + # + # Validate Config + # + tmp = get_config_value(interface, 'interface') + self.assertEqual(interface, tmp) + + tmp = get_config_value(interface, 'hw_mode') + # rewrite special mode + if mode == 'n': mode = 'g' + self.assertEqual(mode, tmp) + + # WPA key + tmp = get_config_value(interface, 'wpa') + self.assertEqual('2', tmp) + tmp = get_config_value(interface, 'wpa_passphrase') + self.assertEqual(wpa_key, tmp) + + # SSID + tmp = get_config_value(interface, 'ssid') + self.assertEqual(ssid, tmp) + + # channel + tmp = get_config_value(interface, 'channel') + self.assertEqual(channel, tmp) + + # Country code + tmp = get_config_value(interface, 'country_code') + self.assertEqual(country, tmp) + + # Check for running process + self.assertTrue(process_named_running('hostapd')) + if __name__ == '__main__': check_kmod('mac80211_hwsim') 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_snmp.py b/smoketest/scripts/cli/test_service_snmp.py index 067a3c76b..2c2e2181b 100755 --- a/smoketest/scripts/cli/test_service_snmp.py +++ b/smoketest/scripts/cli/test_service_snmp.py @@ -18,10 +18,10 @@ import os import re import unittest -from vyos.validate import is_ipv4 from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError +from vyos.template import is_ipv4 from vyos.util import read_file from vyos.util import process_named_running 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/smoketest/scripts/cli/test_service_tftp-server.py b/smoketest/scripts/cli/test_service_tftp-server.py index 92333392a..3210e622f 100755 --- a/smoketest/scripts/cli/test_service_tftp-server.py +++ b/smoketest/scripts/cli/test_service_tftp-server.py @@ -24,7 +24,7 @@ from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.util import read_file from vyos.util import process_named_running -from vyos.validate import is_ipv6 +from vyos.template import is_ipv6 PROCESS_NAME = 'in.tftpd' base_path = ['service', 'tftp-server'] diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py index 4f62b62d5..822a9aff2 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_system_ntp.py @@ -20,8 +20,8 @@ import unittest from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError -from vyos.template import vyos_address_from_cidr -from vyos.template import vyos_netmask_from_cidr +from vyos.template import address_from_cidr +from vyos.template import netmask_from_cidr from vyos.util import read_file from vyos.util import process_named_running @@ -86,8 +86,8 @@ class TestSystemNTP(unittest.TestCase): # Check generated client address configuration for network in networks: - network_address = vyos_address_from_cidr(network) - network_netmask = vyos_netmask_from_cidr(network) + network_address = address_from_cidr(network) + network_netmask = netmask_from_cidr(network) tmp = get_config_value(f'restrict {network_address}')[0] test = f'mask {network_netmask} nomodify notrap nopeer' diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 1777d4db7..c2868e078 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -22,10 +22,11 @@ from copy import deepcopy from vyos.config import Config from vyos.template import render +from vyos.template import is_ipv6 from vyos.util import call -from vyos.validate import is_subnet_connected, is_ipv6 -from vyos import ConfigError +from vyos.validate import is_subnet_connected +from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 2187b3c73..ef52cbfd3 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -21,12 +21,12 @@ from sys import exit from vyos.config import Config from vyos.configdict import dict_merge from vyos.hostsd_client import Client as hostsd_client +from vyos.template import render +from vyos.template import is_ipv6 from vyos.util import call from vyos.util import chown -from vyos.util import vyos_dict_search -from vyos.template import render +from vyos.util import dict_search from vyos.xml import defaults -from vyos.validate import is_ipv6 from vyos import ConfigError from vyos import airbag @@ -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..076bdb63e 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -18,12 +18,15 @@ import os from sys import exit from netifaces import interfaces +import re from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import node_changed +from vyos.configdict import leaf_node_changed from vyos.configdict import is_member from vyos.configdict import is_source_interface +from vyos.configdict import has_vlan_subinterface_configured from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_vrf @@ -32,12 +35,32 @@ 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 airbag.enable() +def helper_check_removed_vlan(conf,bridge,key,key_mangling): + key_update = re.sub(key_mangling[0], key_mangling[1], key) + if dict_search('member.interface', bridge): + for interface in bridge['member']['interface']: + tmp = leaf_node_changed(conf, ['member', 'interface',interface,key]) + if tmp: + if 'member' in bridge: + if 'interface' in bridge['member']: + if interface in bridge['member']['interface']: + bridge['member']['interface'][interface].update({f'{key_update}_removed': tmp }) + else: + bridge['member']['interface'].update({interface: {f'{key_update}_removed': tmp }}) + else: + bridge['member'].update({ 'interface': {interface: {f'{key_update}_removed': tmp }}}) + else: + bridge.update({'member': { 'interface': {interface: {f'{key_update}_removed': tmp }}}}) + + return bridge + + def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -57,8 +80,14 @@ def get_config(config=None): bridge['member'].update({'interface_remove': tmp }) else: bridge.update({'member': {'interface_remove': tmp }}) - - if vyos_dict_search('member.interface', bridge): + + + # determine which members vlan have been removed + + bridge = helper_check_removed_vlan(conf,bridge,'native-vlan',('-', '_')) + bridge = helper_check_removed_vlan(conf,bridge,'allowed-vlan',('-', '_')) + + 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']): @@ -70,7 +99,8 @@ def get_config(config=None): # the default dictionary is not properly paged into the dict (see T2665) # thus we will ammend it ourself default_member_values = defaults(base + ['member', 'interface']) - for interface in bridge['member']['interface']: + vlan_aware = False + for interface,interface_config in bridge['member']['interface'].items(): bridge['member']['interface'][interface] = dict_merge( default_member_values, bridge['member']['interface'][interface]) @@ -90,6 +120,19 @@ def get_config(config=None): # Bridge members must not have an assigned address tmp = has_address_configured(conf, interface) if tmp: bridge['member']['interface'][interface].update({'has_address' : ''}) + + # VLAN-aware bridge members must not have VLAN interface configuration + if 'native_vlan' in interface_config: + if 'disable' not in interface_config['native_vlan']: + vlan_aware = True + + if 'allowed_vlan' in interface_config: + vlan_aware = True + + if vlan_aware: + tmp = has_vlan_subinterface_configured(conf,interface) + if tmp: + if tmp: bridge['member']['interface'][interface].update({'has_vlan' : ''}) return bridge @@ -100,7 +143,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, ' @@ -121,6 +164,21 @@ def verify(bridge): if 'has_address' in interface_config: raise ConfigError(error_msg + 'it has an address assigned!') + + if 'has_vlan' in interface_config: + raise ConfigError(error_msg + 'it has an VLAN subinterface assigned!') + + if 'allowed_vlan' in interface_config: + for vlan in interface_config['allowed_vlan']: + if re.search('[0-9]{1,4}-[0-9]{1,4}', vlan): + vlan_range = vlan.split('-') + if int(vlan_range[0]) <1 and int(vlan_range[0])>4094: + raise ConfigError('VLAN ID must be between 1 and 4094') + if int(vlan_range[1]) <1 and int(vlan_range[1])>4094: + raise ConfigError('VLAN ID must be between 1 and 4094') + else: + if int(vlan) <1 and int(vlan)>4094: + raise ConfigError('VLAN ID must be between 1 and 4094') return None diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 3f4965029..7d5f7f3a0 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.template import is_ipv4 +from vyos.template import is_ipv6 +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 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})') + 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.') - 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') - - 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 dict_search('server.reject_unconfigured_clients', openvpn): + raise ConfigError('Option reject-unconfigured-clients only supported in server mode') - if openvpn['server_topology']: - raise ConfigError('The "topology" option is only valid 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') + + 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_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.cert_file', openvpn) + if file and not checkCertHeader('-----BEGIN CERTIFICATE-----', file): + raise ConfigError(f'Specified cert-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.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_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.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_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.crl_file', openvpn) + if file and not checkCertHeader('-----BEGIN X509 CRL-----', file): + raise ConfigError(f'Specified crl-file "{file} not valid') - 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.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_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'])) + if file and not verify_diffie_hellman_length(file, 2048): + raise ConfigError(f'Minimum DH key-size is 2048 bits') - if openvpn['tls_role']: + 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,25 @@ 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) + # create client config directory on demand + if not os.path.exists(ccd_dir): + os.makedirs(ccd_dir, 0o755) + chown(ccd_dir, 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 +476,24 @@ 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['server_subnet'] = dict_search('server.subnet', openvpn) + + import pprint + pprint.pprint(client_config) + + render(client_file, 'openvpn/client.conf.tmpl', client_config, + trim_blocks=True, user=user, group=group) # 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(""", '"')) - chown(get_config_name(interface), user, group) + render(cfg_file.format(**openvpn), 'openvpn/server.conf.tmpl', openvpn, + trim_blocks=True, formater=lambda _: _.replace(""", '"'), + user=user, group=group) # Fixup file permissions for file in fix_permissions: @@ -1031,75 +502,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 +543,4 @@ if __name__ == '__main__': except ConfigError as e: print(e) exit(1) + diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 5561514bd..f1217b62d 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -23,12 +23,14 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import is_member +from vyos.configdict import list_diff +from vyos.dicts import FixedDict from vyos.ifconfig import Interface, GREIf, GRETapIf, IPIPIf, IP6GREIf, IPIP6If, IP6IP6If, SitIf, Sit6RDIf from vyos.ifconfig.afi import IP4, IP6 -from vyos.configdict import list_diff -from vyos.validate import is_ipv4, is_ipv6 +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 from vyos import ConfigError -from vyos.dicts import FixedDict + from vyos import airbag airbag.enable() diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index c1770771e..5d723bbfd 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'] @@ -109,20 +109,15 @@ def get_config(config=None): if tmp: wifi = dict_merge(tmp, wifi) - # retrieve configured regulatory domain - conf.set_level(['system']) - if conf.exists(['wifi-regulatory-domain']): - wifi['country_code'] = conf.return_value(['wifi-regulatory-domain']) - # Only one wireless interface per phy can be in station mode tmp = find_other_stations(conf, base, wifi['ifname']) 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]) @@ -144,8 +139,7 @@ def verify(wifi): if wifi['type'] == 'access-point': if 'country_code' not in wifi: - raise ConfigError('Wireless regulatory domain is mandatory,\n' \ - 'use "set system wifi-regulatory-domain" for configuration.') + raise ConfigError('Wireless country-code is mandatory') if 'channel' not in wifi: raise ConfigError('Wireless channel must be configured!') @@ -241,7 +235,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/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 7d8110096..bce3405d0 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -42,6 +42,7 @@ def get_config(config=None): conf = Config() base = ['interfaces', 'wirelessmodem'] wwan = get_interface_dict(conf, base) + return wwan def verify(wwan): @@ -56,7 +57,8 @@ def verify(wwan): # we can not use isfile() here as Linux device files are no regular files # thus the check will return False - if not os.path.exists(find_device_file(wwan['device'])): + dev_path = find_device_file(wwan['device']) + if dev_path is None or not os.path.exists(dev_path): raise ConfigError('Device "{device}" does not exist'.format(**wwan)) verify_vrf(wwan) @@ -89,21 +91,21 @@ def generate(wwan): wwan['device'] = find_device_file(wwan['device']) # Create PPP configuration files - render(config_wwan, 'wwan/peer.tmpl', wwan) + render(config_wwan, 'wwan/peer.tmpl', wwan, trim_blocks=True) # Create PPP chat script - render(config_wwan_chat, 'wwan/chat.tmpl', wwan) + render(config_wwan_chat, 'wwan/chat.tmpl', wwan, trim_blocks=True) # generated script file must be executable # Create script for ip-pre-up.d render(script_wwan_pre_up, 'wwan/ip-pre-up.script.tmpl', - wwan, permission=0o755) + wwan, trim_blocks=True, permission=0o755) # Create script for ip-up.d render(script_wwan_ip_up, 'wwan/ip-up.script.tmpl', - wwan, permission=0o755) + wwan, trim_blocks=True, permission=0o755) # Create script for ip-down.d render(script_wwan_ip_down, 'wwan/ip-down.script.tmpl', - wwan, permission=0o755) + wwan, trim_blocks=True, permission=0o755) return None diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index c8e791c78..d1e551cad 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -20,11 +20,11 @@ from sys import exit from copy import deepcopy from vyos.config import Config -from vyos.validate import is_ipv6_link_local, is_ipv6 -from vyos import ConfigError -from vyos.util import call +from vyos.template import is_ipv6 from vyos.template import render - +from vyos.util import call +from vyos.validate import is_ipv6_link_local +from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 1978adff5..654874232 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -32,8 +32,13 @@ def get_config(): conf = Config() base = ['protocols', 'nbgp'] bgp = conf.get_config_dict(base, key_mangling=('-', '_')) + if not conf.exists(base): - return None + bgp = {} + call('vtysh -c \"conf t\" -c \"no ip protocol bgp\" ') + + if not conf.exists(base + ['route-map']): + call('vtysh -c \"conf t\" -c \"no ip protocol bgp\" ') from pprint import pprint pprint(bgp) @@ -44,10 +49,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 +69,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..84948baf4 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -37,60 +37,124 @@ def get_config(config=None): mpls_conf = { 'router_id' : None, 'mpls_ldp' : False, + 'old_parameters' : { + 'no_ttl_propagation' : False, + 'maximum_ttl' : None + }, + 'parameters' : { + 'no_ttl_propagation' : False, + 'maximum_ttl' : None + }, 'old_ldp' : { - 'interfaces' : [], - 'neighbors' : {}, - 'd_transp_ipv4' : None, - 'd_transp_ipv6' : None, - 'hello_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, + 'cisco_interop_tlv' : False, + 'transport_prefer_ipv4' : False, + 'target_ipv4_addresses' : [], + 'target_ipv6_addresses' : [], + 'target_ipv4_enable' : False, + 'target_ipv6_enable' : False, + 'target_ipv4_hello_int' : None, + 'target_ipv6_hello_int' : None, + 'target_ipv4_hello_hold' : None, + 'target_ipv6_hello_hold' : None }, 'ldp' : { - 'interfaces' : [], - 'neighbors' : {}, - 'd_transp_ipv4' : None, - 'd_transp_ipv6' : None, - 'hello_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, + 'cisco_interop_tlv' : False, + 'transport_prefer_ipv4' : False, + 'target_ipv4_addresses' : [], + 'target_ipv6_addresses' : [], + 'target_ipv4_enable' : False, + 'target_ipv6_enable' : False, + 'target_ipv4_hello_int' : None, + 'target_ipv6_hello_int' : None, + 'target_ipv4_hello_hold' : None, + 'target_ipv6_hello_hold' : None } } if not (conf.exists('protocols mpls') or conf.exists_effective('protocols mpls')): return None - + + # If LDP is defined then enable LDP portion of code if conf.exists('protocols mpls ldp'): mpls_conf['mpls_ldp'] = True + # Set to MPLS hierarchy configuration level + conf.set_level('protocols mpls') + + # Get no_ttl_propagation + if conf.exists_effective('parameters no-propagate-ttl'): + mpls_conf['old_parameters']['no_ttl_propagation'] = True + + if conf.exists('parameters no-propagate-ttl'): + mpls_conf['parameters']['no_ttl_propagation'] = True + + # Get maximum_ttl + if conf.exists_effective('parameters maximum-ttl'): + mpls_conf['old_parameters']['maximum_ttl'] = conf.return_effective_value('parameters maximum-ttl') + + if conf.exists('parameters maximum-ttl'): + mpls_conf['parameters']['maximum_ttl'] = conf.return_value('parameters maximum-ttl') + + # Set to LDP hierarchy configuration level conf.set_level('protocols mpls ldp') # Get router-id if conf.exists_effective('router-id'): mpls_conf['old_router_id'] = conf.return_effective_value('router-id') + if conf.exists('router-id'): mpls_conf['router_id'] = conf.return_value('router-id') - # Get hello 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-holdtime'): - mpls_conf['ldp']['hello_holdtime'] = conf.return_value('discovery hello-holdtime') + if conf.exists('discovery hello-ipv4-holdtime'): + mpls_conf['ldp']['hello_ipv4_holdtime'] = conf.return_value('discovery hello-ipv4-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-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-interval'): - mpls_conf['ldp']['hello_interval'] = conf.return_value('discovery hello-interval') + if conf.exists('discovery hello-ipv4-interval'): + mpls_conf['ldp']['hello_ipv4_interval'] = conf.return_value('discovery hello-ipv4-interval') + + # Get hello-ipv6-holdtime + if conf.exists_effective('discovery hello-ipv6-holdtime'): + mpls_conf['old_ldp']['hello_ipv6_holdtime'] = conf.return_effective_value('discovery hello-ipv6-holdtime') + + if conf.exists('discovery hello-ipv6-holdtime'): + mpls_conf['ldp']['hello_ipv6_holdtime'] = conf.return_value('discovery hello-ipv6-holdtime') + + # Get hello-ipv6-interval + if conf.exists_effective('discovery hello-ipv6-interval'): + mpls_conf['old_ldp']['hello_ipv6_interval'] = conf.return_effective_value('discovery hello-ipv6-interval') + + if conf.exists('discovery hello-ipv6-interval'): + mpls_conf['ldp']['hello_ipv6_interval'] = conf.return_value('discovery hello-ipv6-interval') # Get session-ipv4-holdtime if conf.exists_effective('discovery session-ipv4-holdtime'): @@ -134,6 +198,76 @@ def get_config(config=None): if conf.exists('export ipv6 explicit-null'): mpls_conf['ldp']['export_ipv6_exp'] = True + # Get target_ipv4_addresses + if conf.exists_effective('targeted-neighbor ipv4 address'): + mpls_conf['old_ldp']['target_ipv4_addresses'] = conf.return_effective_values('targeted-neighbor ipv4 address') + + if conf.exists('targeted-neighbor ipv4 address'): + mpls_conf['ldp']['target_ipv4_addresses'] = conf.return_values('targeted-neighbor ipv4 address') + + # Get target_ipv4_enable + if conf.exists_effective('targeted-neighbor ipv4 enable'): + mpls_conf['old_ldp']['target_ipv4_enable'] = True + + if conf.exists('targeted-neighbor ipv4 enable'): + mpls_conf['ldp']['target_ipv4_enable'] = True + + # Get target_ipv4_hello_int + if conf.exists_effective('targeted-neighbor ipv4 hello-interval'): + mpls_conf['old_ldp']['target_ipv4_hello_int'] = conf.return_effective_value('targeted-neighbor ipv4 hello-interval') + + if conf.exists('targeted-neighbor ipv4 hello-interval'): + mpls_conf['ldp']['target_ipv4_hello_int'] = conf.return_value('targeted-neighbor ipv4 hello-interval') + + # Get target_ipv4_hello_hold + if conf.exists_effective('targeted-neighbor ipv4 hello-holdtime'): + mpls_conf['old_ldp']['target_ipv4_hello_hold'] = conf.return_effective_value('targeted-neighbor ipv4 hello-holdtime') + + if conf.exists('targeted-neighbor ipv4 hello-holdtime'): + mpls_conf['ldp']['target_ipv4_hello_hold'] = conf.return_value('targeted-neighbor ipv4 hello-holdtime') + + # Get target_ipv6_addresses + if conf.exists_effective('targeted-neighbor ipv6 address'): + mpls_conf['old_ldp']['target_ipv6_addresses'] = conf.return_effective_values('targeted-neighbor ipv6 address') + + if conf.exists('targeted-neighbor ipv6 address'): + mpls_conf['ldp']['target_ipv6_addresses'] = conf.return_values('targeted-neighbor ipv6 address') + + # Get target_ipv6_enable + if conf.exists_effective('targeted-neighbor ipv6 enable'): + mpls_conf['old_ldp']['target_ipv6_enable'] = True + + if conf.exists('targeted-neighbor ipv6 enable'): + mpls_conf['ldp']['target_ipv6_enable'] = True + + # Get target_ipv6_hello_int + if conf.exists_effective('targeted-neighbor ipv6 hello-interval'): + mpls_conf['old_ldp']['target_ipv6_hello_int'] = conf.return_effective_value('targeted-neighbor ipv6 hello-interval') + + if conf.exists('targeted-neighbor ipv6 hello-interval'): + mpls_conf['ldp']['target_ipv6_hello_int'] = conf.return_value('targeted-neighbor ipv6 hello-interval') + + # Get target_ipv6_hello_hold + if conf.exists_effective('targeted-neighbor ipv6 hello-holdtime'): + mpls_conf['old_ldp']['target_ipv6_hello_hold'] = conf.return_effective_value('targeted-neighbor ipv6 hello-holdtime') + + if conf.exists('targeted-neighbor ipv6 hello-holdtime'): + mpls_conf['ldp']['target_ipv6_hello_hold'] = conf.return_value('targeted-neighbor ipv6 hello-holdtime') + + # Get parameters cisco-interop-tlv + if conf.exists_effective('parameters cisco-interop-tlv'): + mpls_conf['old_ldp']['cisco_interop_tlv'] = True + + if conf.exists('parameters cisco-interop-tlv'): + mpls_conf['ldp']['cisco_interop_tlv'] = True + + # Get parameters transport-prefer-ipv4 + if conf.exists_effective('parameters transport-prefer-ipv4'): + mpls_conf['old_ldp']['transport_prefer_ipv4'] = True + + if conf.exists('parameters transport-prefer-ipv4'): + mpls_conf['ldp']['transport_prefer_ipv4'] = True + # Get interfaces if conf.exists_effective('interface'): mpls_conf['old_ldp']['interfaces'] = conf.return_effective_values('interface') @@ -145,14 +279,18 @@ def get_config(config=None): for neighbor in conf.list_effective_nodes('neighbor'): mpls_conf['old_ldp']['neighbors'].update({ neighbor : { - 'password' : conf.return_effective_value('neighbor {0} password'.format(neighbor)) + 'password' : conf.return_effective_value('neighbor {0} password'.format(neighbor), default=''), + 'ttl_security' : conf.return_effective_value('neighbor {0} ttl-security'.format(neighbor), default=''), + 'session_holdtime' : conf.return_effective_value('neighbor {0} session-holdtime'.format(neighbor), default='') } }) for neighbor in conf.list_nodes('neighbor'): mpls_conf['ldp']['neighbors'].update({ neighbor : { - 'password' : conf.return_value('neighbor {0} password'.format(neighbor)) + 'password' : conf.return_value('neighbor {0} password'.format(neighbor), default=''), + 'ttl_security' : conf.return_value('neighbor {0} ttl-security'.format(neighbor), default=''), + 'session_holdtime' : conf.return_value('neighbor {0} session-holdtime'.format(neighbor), default='') } }) @@ -172,15 +310,15 @@ def verify(mpls): return None if mpls['mpls_ldp']: - # Requre router-id + # Require router-id if not mpls['router_id']: raise ConfigError(f"MPLS ldp router-id is mandatory!") - # Requre discovery transport-address + # Require discovery transport-address if not mpls['ldp']['d_transp_ipv4'] and not mpls['ldp']['d_transp_ipv6']: raise ConfigError(f"MPLS ldp discovery transport address is mandatory!") - # Requre interface + # Require interface if not mpls['ldp']['interfaces']: raise ConfigError(f"MPLS ldp interface is mandatory!") @@ -195,15 +333,24 @@ def apply(mpls): if mpls is None: return None - # Set number of entries in the platform label table + # Set number of entries in the platform label table if mpls['mpls_ldp']: sysctl('net.mpls.platform_labels', '1048575') else: sysctl('net.mpls.platform_labels', '0') - # Do not copy IP TTL to MPLS header - sysctl('net.mpls.ip_ttl_propagate', '0') - + # Choose whether to copy IP TTL to MPLS header TTL + if mpls['parameters']['no_ttl_propagation']: + sysctl('net.mpls.ip_ttl_propagate', '0') + else: + sysctl('net.mpls.ip_ttl_propagate', '1') + + # Choose whether to limit maximum MPLS header TTL + if mpls['parameters']['maximum_ttl']: + sysctl('net.mpls.default_ttl', '%s' %(mpls['parameters']['maximum_ttl'])) + else: + sysctl('net.mpls.default_ttl', '255') + # Allow mpls on interfaces operate_mpls_on_intfc(mpls['ldp']['interfaces'], 1) diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index 87c7754f3..68c554360 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -23,8 +23,9 @@ from sys import exit from vyos.config import Config from vyos.template import render +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 from vyos.util import call, get_half_cpus -from vyos.validate import is_ipv4 from vyos import ConfigError from vyos import airbag 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/snmp.py b/src/conf_mode/snmp.py index 117bf0274..3990e5735 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -22,8 +22,9 @@ from vyos.config import Config from vyos.configverify import verify_vrf from vyos.snmpv3_hashgen import plaintext_to_md5, plaintext_to_sha1, random from vyos.template import render +from vyos.template import is_ipv4 from vyos.util import call, chmod_755 -from vyos.validate import is_ipv4, is_addr_assigned +from vyos.validate import is_addr_assigned from vyos.version import get_version_data from vyos import ConfigError, airbag airbag.enable() diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index a19fa72d8..e07745963 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -16,15 +16,15 @@ import os -from netifaces import interfaces 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 +54,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/system-options.py b/src/conf_mode/system-options.py index 6ac35a4ab..22765cef7 100755 --- a/src/conf_mode/system-options.py +++ b/src/conf_mode/system-options.py @@ -21,7 +21,7 @@ from sys import exit from vyos.config import Config from vyos.template import render -from vyos.util import call +from vyos.util import cmd from vyos.validate import is_addr_assigned from vyos import ConfigError from vyos import airbag @@ -71,9 +71,9 @@ def generate(options): def apply(options): # Beep action if 'beep_if_fully_booted' in options.keys(): - call('systemctl enable vyos-beep.service') + cmd('systemctl enable vyos-beep.service') else: - call('systemctl disable vyos-beep.service') + cmd('systemctl disable vyos-beep.service') # Ctrl-Alt-Delete action if os.path.exists(systemd_action_file): @@ -100,6 +100,13 @@ def apply(options): else: f.write('0') + # tuned - performance tuning + if 'performance' in options: + cmd('systemctl enable tuned.service') + cmd('tuned-adm profile network-{performance}'.format(**options)) + else: + cmd('systemctl disable tuned.service') + if __name__ == '__main__': try: c = get_config() diff --git a/src/conf_mode/system-wifi-regdom.py b/src/conf_mode/system-wifi-regdom.py deleted file mode 100755 index 874f93923..000000000 --- a/src/conf_mode/system-wifi-regdom.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019-2020 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os - -from copy import deepcopy -from sys import exit - -from vyos.config import Config -from vyos import ConfigError -from vyos.template import render - -from vyos import airbag -airbag.enable() - -config_80211_file='/etc/modprobe.d/cfg80211.conf' -config_crda_file='/etc/default/crda' - -default_config_data = { - 'regdom' : '', - 'deleted' : False -} - -def get_config(config=None): - regdom = deepcopy(default_config_data) - if config: - conf = config - else: - conf = Config() - base = ['system', 'wifi-regulatory-domain'] - - # Check if interface has been removed - if not conf.exists(base): - regdom['deleted'] = True - return regdom - else: - regdom['regdom'] = conf.return_value(base) - - return regdom - -def verify(regdom): - if regdom['deleted']: - return None - - if not regdom['regdom']: - raise ConfigError("Wireless regulatory domain is mandatory.") - - return None - -def generate(regdom): - print("Changing the wireless regulatory domain requires a system reboot.") - - if regdom['deleted']: - if os.path.isfile(config_80211_file): - os.unlink(config_80211_file) - - if os.path.isfile(config_crda_file): - os.unlink(config_crda_file) - - return None - - render(config_80211_file, 'wifi/cfg80211.conf.tmpl', regdom) - render(config_crda_file, 'wifi/crda.tmpl', regdom) - return None - -def apply(regdom): - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index cac95afe2..56e195b6a 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -25,9 +25,9 @@ from sys import exit from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render +from vyos.template import is_ipv4 from vyos.util import call from vyos.util import chmod_755 -from vyos.validate import is_ipv4 from vyos.validate import is_addr_assigned from vyos.xml import defaults from vyos import ConfigError diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index da51b0d06..80eb8daf2 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -25,10 +25,10 @@ from time import sleep from ipaddress import ip_network from vyos.config import Config +from vyos.template import is_ipv4 +from vyos.template import render from vyos.util import call, get_half_cpus -from vyos.validate import is_ipv4 from vyos import ConfigError -from vyos.template import render from vyos import airbag airbag.enable() @@ -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/etc/udev/rules.d/99-vyos-wwan.rules b/src/etc/udev/rules.d/99-vyos-wwan.rules deleted file mode 100644 index 67f30a3dd..000000000 --- a/src/etc/udev/rules.d/99-vyos-wwan.rules +++ /dev/null @@ -1,11 +0,0 @@ -ACTION!="add|change", GOTO="mbim_to_qmi_rules_end" - -SUBSYSTEM!="usb", GOTO="mbim_to_qmi_rules_end" - -# ignore any device with only one configuration -ATTR{bNumConfigurations}=="1", GOTO="mbim_to_qmi_rules_end" - -# force Sierra Wireless MC7710 to configuration #1 -ATTR{idVendor}=="1199",ATTR{idProduct}=="68a2",ATTR{bConfigurationValue}="1" - -LABEL="mbim_to_qmi_rules_end" diff --git a/src/migration-scripts/interfaces/13-to-14 b/src/migration-scripts/interfaces/13-to-14 new file mode 100755 index 000000000..fc6d7f443 --- /dev/null +++ b/src/migration-scripts/interfaces/13-to-14 @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# T3043: rename Wireless interface security mode 'both' to 'wpa+wpa2' +# T3043: move "system wifi-regulatory-domain" to indicidual wireless interface + +import os + +from sys import exit, argv +from vyos.configtree import ConfigTree + +if __name__ == '__main__': + if (len(argv) < 1): + print("Must specify file name!") + exit(1) + + file_name = argv[1] + with open(file_name, 'r') as f: + config_file = f.read() + + config = ConfigTree(config_file) + base = ['interfaces', 'wireless'] + if not config.exists(base): + # Nothing to do + exit(0) + + country_code = '' + cc_cli = ['system', 'wifi-regulatory-domain'] + if config.exists(cc_cli): + country_code = config.return_value(cc_cli) + config.delete(cc_cli) + + for wifi in config.list_nodes(base): + sec_mode = base + [wifi, 'security', 'wpa', 'mode'] + if config.exists(sec_mode): + mode = config.return_value(sec_mode) + if mode == 'both': + config.set(sec_mode, value='wpa+wpa2', replace=True) + + if country_code: + config.set(base + [wifi, 'country-code'], value=country_code) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) diff --git a/src/op_mode/show_interfaces.py b/src/op_mode/show_interfaces.py index d4dae3cd1..de41274a7 100755 --- a/src/op_mode/show_interfaces.py +++ b/src/op_mode/show_interfaces.py @@ -217,8 +217,9 @@ def run_show_intf_brief(ifnames, iftypes, vif, vrrp): admin_state = interface.get_admin_state() intf = [interface.ifname,] - oper = ['u', ] if oper_state in ('up', 'unknown') else ['A', ] - admin = ['u', ] if oper_state in ('up', 'unknown') else ['D', ] + + oper = ['u', ] if oper_state in ('up', 'unknown') else ['D', ] + admin = ['u', ] if admin_state in ('up', 'unknown') else ['A', ] addrs = [_ for _ in interface.get_addr() if not _.startswith('fe80::')] or ['-', ] descs = list(split_text(interface.get_alias(),0)) @@ -226,8 +227,8 @@ def run_show_intf_brief(ifnames, iftypes, vif, vrrp): i = intf.pop(0) if intf else '' a = addrs.pop(0) if addrs else '' d = descs.pop(0) if descs else '' - s = [oper.pop(0)] if oper else [] - l = [admin.pop(0)] if admin else [] + s = [admin.pop(0)] if admin else [] + l = [oper.pop(0)] if oper else [] if len(a) < 33: print(format1 % (i, a, '/'.join(s+l), d)) else: 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/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..8a7241fe3 --- /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 address_from_cidr +from vyos.template import netmask_from_cidr +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.template import first_host_address +from vyos.template import last_host_address +from vyos.template import inc_ip + +class TestTeamplteHelpers(TestCase): + def setUp(self): + pass + + def test_helpers_from_cidr(self): + network_v4 = '192.0.2.0/26' + self.assertEqual(address_from_cidr(network_v4), str(ip_network(network_v4).network_address)) + self.assertEqual(netmask_from_cidr(network_v4), str(ip_network(network_v4).netmask)) + + def test_helpers_ipv4(self): + self.assertTrue(is_ipv4('192.0.2.1')) + self.assertTrue(is_ipv4('192.0.2.0/24')) + self.assertTrue(is_ipv4('192.0.2.1/32')) + self.assertTrue(is_ipv4('10.255.1.2')) + self.assertTrue(is_ipv4('10.255.1.0/24')) + self.assertTrue(is_ipv4('10.255.1.2/32')) + self.assertFalse(is_ipv4('2001:db8::')) + self.assertFalse(is_ipv4('2001:db8::1')) + self.assertFalse(is_ipv4('2001:db8::/64')) + + def test_helpers_ipv6(self): + self.assertFalse(is_ipv6('192.0.2.1')) + self.assertFalse(is_ipv6('192.0.2.0/24')) + self.assertFalse(is_ipv6('192.0.2.1/32')) + self.assertFalse(is_ipv6('10.255.1.2')) + self.assertFalse(is_ipv6('10.255.1.0/24')) + self.assertFalse(is_ipv6('10.255.1.2/32')) + self.assertTrue(is_ipv6('2001:db8::')) + self.assertTrue(is_ipv6('2001:db8::1')) + self.assertTrue(is_ipv6('2001:db8::1/64')) + self.assertTrue(is_ipv6('2001:db8::/32')) + self.assertTrue(is_ipv6('2001:db8::/64')) + + def test_helpers_first_host_address(self): + self.assertEqual(first_host_address('10.0.0.0/24'), '10.0.0.1') + self.assertEqual(first_host_address('10.0.0.128/25'), '10.0.0.129') + self.assertEqual(first_host_address('10.0.0.200/29'), '10.0.0.201') + + self.assertEqual(first_host_address('2001:db8::/64'), '2001:db8::') + self.assertEqual(first_host_address('2001:db8::/112'), '2001:db8::') + self.assertEqual(first_host_address('2001:db8::10/112'), '2001:db8::10') + self.assertEqual(first_host_address('2001:db8::100/112'), '2001:db8::100') diff --git a/src/tests/test_template.py b/src/tests/test_template.py new file mode 100644 index 000000000..6dc2f075e --- /dev/null +++ b/src/tests/test_template.py @@ -0,0 +1,46 @@ +#!/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.template +from unittest import TestCase + +class TestVyOSTemplate(TestCase): + def setUp(self): + pass + + def test_is_ip(self): + self.assertTrue(vyos.template.is_ip('192.0.2.1')) + self.assertTrue(vyos.template.is_ip('2001:db8::1')) + self.assertFalse(vyos.template.is_ip('VyOS')) + + def test_is_ipv4(self): + self.assertTrue(vyos.template.is_ipv4('192.0.2.1')) + self.assertTrue(vyos.template.is_ipv4('192.0.2.0/24')) + self.assertTrue(vyos.template.is_ipv4('192.0.2.1/32')) + + self.assertFalse(vyos.template.is_ipv4('2001:db8::1')) + self.assertFalse(vyos.template.is_ipv4('2001:db8::/64')) + self.assertFalse(vyos.template.is_ipv4('VyOS')) + + def test_is_ipv6(self): + self.assertTrue(vyos.template.is_ipv6('2001:db8::1')) + self.assertTrue(vyos.template.is_ipv6('2001:db8::/64')) + self.assertTrue(vyos.template.is_ipv6('2001:db8::1/64')) + + self.assertFalse(vyos.template.is_ipv6('192.0.2.1')) + self.assertFalse(vyos.template.is_ipv6('192.0.2.0/24')) + self.assertFalse(vyos.template.is_ipv6('192.0.2.1/32')) + self.assertFalse(vyos.template.is_ipv6('VyOS')) 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..226e856a3 --- /dev/null +++ b/src/tests/test_validate.py @@ -0,0 +1,41 @@ +#!/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_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/src/validators/ipv4-range b/src/validators/ipv4-range index ae3f3f163..cc59039f1 100755 --- a/src/validators/ipv4-range +++ b/src/validators/ipv4-range @@ -28,6 +28,8 @@ if [[ "$1" =~ "-" ]]; then if [ $start -ge $stop ]; then exit 1 fi + + exit 0 fi -exit 0 +exit 1 diff --git a/vyos-configtest b/vyos-configtest new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/vyos-configtest |