diff options
86 files changed, 2009 insertions, 1372 deletions
diff --git a/.github/reviewers.yml b/.github/reviewers.yml new file mode 100644 index 000000000..9ef3ec961 --- /dev/null +++ b/.github/reviewers.yml @@ -0,0 +1,34 @@ +--- +python/**: + - c-po + - dmbaturin + - jestabro + +interface-definitions/**: + - c-po + - DmitriyEshenko + - dmbaturin + - jestabro + - sever-sever + - zdc + +op-mode-definitions/**: + - c-po + - DmitriyEshenko + - dmbaturin + - jestabro + - sever-sever + - zdc + +src/**: + - c-po + - DmitriyEshenko + - dmbaturin + - jestabro + - sever-sever + - zdc + +.github/**: + - c-po + - dmbaturin + - UnicronNL diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml new file mode 100644 index 000000000..81134206b --- /dev/null +++ b/.github/workflows/auto-author-assign.yml @@ -0,0 +1,27 @@ +name: "PR Triage" +on: + pull_request_target: + types: [opened, reopened, ready_for_review, locked] + +permissions: + pull-requests: write + +jobs: + # https://github.com/marketplace/actions/auto-author-assign + assign-author: + runs-on: ubuntu-latest + steps: + - name: "Assign Author to PR" + uses: toshimaru/auto-author-assign@v1.3.5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + # https://github.com/shufo/auto-assign-reviewer-by-files + assign_reviewer: + runs-on: ubuntu-latest + steps: + - name: Request review based on files changes and/or groups the author belongs to + uses: shufo/auto-assign-reviewer-by-files@v1.1.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + config: .github/reviewers.yml diff --git a/.github/workflows/pr-conflicts.yml b/.github/workflows/pr-conflicts.yml new file mode 100644 index 000000000..72ff3969b --- /dev/null +++ b/.github/workflows/pr-conflicts.yml @@ -0,0 +1,18 @@ +name: "PR Conflicts checker" +on: + pull_request_target: + types: [synchronize] + +jobs: + Conflict_Check: + name: 'Check PR status: conflicts and resolution' + runs-on: ubuntu-18.04 + steps: + - name: check if PRs are dirty + uses: eps1lon/actions-label-merge-conflict@releases/2.x + with: + dirtyLabel: "state: conflict" + removeOnDirtyLabel: "state: conflict resolved" + repoToken: "${{ secrets.GITHUB_TOKEN }}" + commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request." + commentOnClean: "Conflicts have been resolved. A maintainer will review the pull request shortly." diff --git a/.gitignore b/.gitignore index e86ef4036..a31d037f2 100644 --- a/.gitignore +++ b/.gitignore @@ -120,6 +120,7 @@ debian/vyos-1x-vmware debian/vyos-1x-smoketest debian/*.postinst.debhelper debian/*.prerm.debhelper +debian/*.postrm.debhelper debian/*.substvars # Sonar Cloud @@ -26,7 +26,7 @@ op_xml_obj = $(op_xml_src:.xml.in=.xml) # -P Inhibit generation of linemarkers in the output from the # preprocessor mkdir -p $(BUILD_DIR)/$(dir $@) - @$(CC) -x c-header -E -undef -nostdinc -P -I$(CURDIR)/$(dir $<) -o $(BUILD_DIR)/$@ -c $< + @$(CC) -x c-header -C -E -undef -nostdinc -P -I$(CURDIR)/$(dir $<) -o $(BUILD_DIR)/$@ -c $< .PHONY: interface_definitions .ONESHELL: diff --git a/data/configd-include.json b/data/configd-include.json index dc82b3dd7..ee939decd 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -26,7 +26,7 @@ "interfaces-vxlan.py", "interfaces-wireguard.py", "interfaces-wireless.py", -"interfaces-wirelessmodem.py", +"interfaces-wwan.py", "ipsec-settings.py", "lldp.py", "nat.py", diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index 158da3605..c21e7f234 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -402,15 +402,18 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none bgp always-compare-med {% endif %} {% if parameters.bestpath is defined and parameters.bestpath is not none %} -{% if parameters.bestpath.compare_routerid is defined %} - bgp bestpath compare-routerid -{% endif %} {% if parameters.bestpath.as_path is defined and parameters.bestpath.as_path is not none %} {% for option in parameters.bestpath.as_path %} {# replace is required for multipath-relax option #} bgp bestpath as-path {{ option|replace('_', '-') }} {% endfor %} {% endif %} +{% if parameters.bestpath.bandwidth is defined and parameters.bestpath.bandwidth is not none %} + bgp bestpath bandwidth {{ parameters.bestpath.bandwidth }} +{% endif %} +{% if parameters.bestpath.compare_routerid is defined %} + bgp bestpath compare-routerid +{% endif %} {% if parameters.bestpath.med is defined and parameters.bestpath.med is not none %} bgp bestpath med {{ 'confed' if parameters.bestpath.med.confed is defined }} {{ 'missing-as-worst' if parameters.bestpath.med.missing_as_worst is defined }} {% endif %} diff --git a/data/templates/frr/policy.frr.tmpl b/data/templates/frr/policy.frr.tmpl index 507ee2a14..b5649b44e 100644 --- a/data/templates/frr/policy.frr.tmpl +++ b/data/templates/frr/policy.frr.tmpl @@ -247,11 +247,14 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }} {% if rule_config.set.distance is defined and rule_config.set.distance is not none %} set distance {{ rule_config.set.distance }} {% endif %} -{% if rule_config.set.extcommunity_rt is defined and rule_config.set.extcommunity_rt is not none %} - set extcommunity rt {{ rule_config.set.extcommunity_rt }} +{% if rule_config.set.extcommunity is defined and rule_config.set.extcommunity.bandwidth is defined and rule_config.set.extcommunity.bandwidth is not none %} + set extcommunity bandwidth {{ rule_config.set.extcommunity.bandwidth }} {% endif %} -{% if rule_config.set.extcommunity_soo is defined and rule_config.set.extcommunity_soo is not none %} - set extcommunity soo {{ rule_config.set.extcommunity_soo }} +{% if rule_config.set.extcommunity is defined and rule_config.set.extcommunity.rt is defined and rule_config.set.extcommunity.rt is not none %} + set extcommunity rt {{ rule_config.set.extcommunity.rt }} +{% endif %} +{% if rule_config.set.extcommunity is defined and rule_config.set.extcommunity.soo is defined and rule_config.set.extcommunity.soo is not none %} + set extcommunity soo {{ rule_config.set.extcommunity.soo }} {% endif %} {% if rule_config.set.ip_next_hop is defined and rule_config.set.ip_next_hop is not none %} set ip next-hop {{ rule_config.set.ip_next_hop }} diff --git a/data/templates/ipsec/ike-esp.tmpl b/data/templates/ipsec/ike-esp.tmpl deleted file mode 100644 index deeb8c80d..000000000 --- a/data/templates/ipsec/ike-esp.tmpl +++ /dev/null @@ -1,32 +0,0 @@ -{% macro conn(ike, ike_ciphers, esp, esp_ciphers) -%} -{% if ike %} -{% if "key_exchange" in ike %} - keyexchange = {{ ike.key_exchange }} -{% endif %} - ike = {{ ike_ciphers }} -{% if "lifetime" in ike %} - ikelifetime = {{ ike.lifetime }}s -{% endif %} - reauth = {{ ike.ikev2_reauth if "ikev2_reauth" in ike else "no" }} - closeaction = {{ ike.close_action if "close_action" in ike else "none" }} -{% if "dead_peer_detection" in ike %} - dpdaction = {{ ike.dead_peer_detection.action }} - dpdtimeout = {{ ike.dead_peer_detection.timeout }} - dpddelay = {{ ike.dead_peer_detection.interval }} -{% endif %} -{% if "key_exchange" in ike and ike.key_exchange == "ikev1" and "mode" in ike and ike.mode == "aggressive" %} - aggressive = yes -{% endif %} -{% if "key_exchange" in ike and ike.key_exchange == "ikev2" %} - mobike = {{ "yes" if "mobike" not in ike or ike.mobike == "enable" else "no" }} -{% endif %} -{% endif %} -{% if esp %} - esp = {{ esp_ciphers }} -{% if "lifetime" in esp %} - keylife = {{ esp.lifetime }}s -{% endif %} - compress = {{ 'yes' if "compression" in esp and esp.compression == 'enable' else 'no' }} - type = {{ esp.mode if "mode" in esp else "tunnel" }} -{% endif %} -{%- endmacro %} diff --git a/data/templates/ipsec/ipsec.conf.tmpl b/data/templates/ipsec/ipsec.conf.tmpl index 342887883..6550ea419 100644 --- a/data/templates/ipsec/ipsec.conf.tmpl +++ b/data/templates/ipsec/ipsec.conf.tmpl @@ -1,113 +1,9 @@ # Created by VyOS - manual changes will be overwritten -{% import 'ipsec/ike-esp.tmpl' as ike_esp %} - config setup charondebug = "{{ charondebug }}" uniqueids = {{ "no" if disable_uniqreqids is defined else "yes" }} -{% if site_to_site is defined and site_to_site.peer is defined %} -{% for peer, peer_conf in site_to_site.peer.items() %} -{% set peer_index = loop.index %} -{% set peer_ike = ike_group[peer_conf.ike_group] %} -{% set peer_esp = esp_group[peer_conf.default_esp_group] if peer_conf.default_esp_group is defined else None %} -conn peer-{{ peer }} -{% if peer_conf.authentication.mode in authby %} - authby = {{ authby[peer_conf.authentication.mode] }} -{% endif %} -{% if peer_conf.authentication.mode == 'x509' %} -{% set cert_file = peer_conf.authentication.x509.cert_file %} - leftcert = {{ cert_file if cert_file.startswith(x509_path) else (x509_path + cert_file) }} - leftsendcert = always - rightca = %same -{% elif peer_conf.authentication.mode == 'rsa' %} - leftsigkey = localhost.pub - rightsigkey = {{ peer_conf.authentication.rsa_key_name }}.pub -{% endif %} - left = {{ peer_conf.local_address if peer_conf.local_address != 'any' else '%defaultroute' }} # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }} -{% if peer_conf.authentication.id is defined and peer_conf.authentication.use_x509_id is not defined %} - leftid = "{{ peer_conf.authentication.id }}" -{% endif %} - right = {{ peer if peer not in ['any', '0.0.0.0'] and peer[0:1] != '@' else '%any' }} -{% if peer_conf.authentication.remote_id is defined %} - rightid = "{{ peer_conf.authentication.remote_id }}" -{% elif peer[0:1] == '@' %} - rightid = "{{ peer }}" -{% endif %} - keylife = 3600s - rekeymargin = 540s -{{ ike_esp.conn(peer_ike, ciphers.ike[peer_conf.ike_group], peer_esp, ciphers.esp[peer_conf.default_esp_group] if peer_esp else None) }} -{% if peer_conf.vti is defined and peer_conf.vti.bind is defined %} -{% set vti_esp = esp_group[peer_conf.vti.esp_group] if peer_conf.vti.esp_group is defined else None %} -conn peer-{{ peer }}-vti - also = peer-{{ peer }} - leftsubnet = 0.0.0.0/0 - leftupdown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }} {{ peer_conf.dhcp_interface if peer_conf.dhcp_interface is defined else 'no' }}" - rightsubnet = 0.0.0.0/0 - mark = {{ marks[peer_conf.vti.bind] }} -{{ ike_esp.conn(None, None, vti_esp, ciphers.esp[peer_conf.vti.esp_group] if vti_esp else None) }} -{% if peer[0:1] == '@' %} - rekey = no - auto = add - keyingtries = %forever -{% else %} -{% if peer_conf.connection_type is not defined or peer_conf.connection_type == 'initiate' %} - auto = start - keyingtries = %forever -{% elif peer_conf.connection_type == 'respond' %} - auto = route - keyingtries = 1 -{% endif %} -{% endif %} -{% elif peer_conf.tunnel is defined %} -{% for tunnel_id, tunnel_conf in peer_conf.tunnel.items() %} -{% set tunnel_esp_name = tunnel_conf.esp_group if "esp_group" in tunnel_conf else peer_conf.default_esp_group %} -{% set tunnel_esp = esp_group[tunnel_esp_name] %} -{% set proto = tunnel_conf.protocol if "protocol" in tunnel_conf else '%any' %} -conn peer-{{ peer }}-tunnel-{{tunnel_id}} - also = peer-{{ peer }} -{% if tunnel_esp.mode is not defined or tunnel_esp.mode == 'tunnel' %} -{% if tunnel_conf.local is defined and tunnel_conf.local.prefix is defined %} - leftsubnet = {{ tunnel_conf.local.prefix if tunnel_conf.local.prefix != 'any' else '0.0.0.0/0' }}[{{ proto }}/{{ tunnel_conf.local.port if "port" in tunnel_conf.local else '%any' }}] -{% endif %} -{% if tunnel_conf.remote is defined and tunnel_conf.remote.prefix is defined %} - rightsubnet = {{ tunnel_conf.remote.prefix if tunnel_conf.remote.prefix != 'any' else '0.0.0.0/0' }}[{{ proto }}/{{ tunnel_conf.remote.port if "port" in tunnel_conf.remote else '%any' }}] -{% endif %} -{% elif tunnel_esp.mode == 'transport' %} - leftsubnet = {{ peer_conf.local_address }}[{{ proto }}/{{ tunnel_conf.local.port if "local" in tunnel_conf and "port" in tunnel_conf.local else '%any' }}] - rightsubnet = {{ peer }}[{{ proto }}/{{ tunnel_conf.local.port if "local" in tunnel_conf and "port" in tunnel_conf.local else '%any' }}] -{% endif %} -{% if tunnel_conf.esp_group is defined %} -{{ ike_esp.conn(None, None, tunnel_esp, ciphers.esp[tunnel_esp_name]) }} -{% endif %} -{% if peer[0:1] == '@' %} - rekey = no - auto = add - keyingtries = %forever -{% else %} -{% if peer_conf.connection_type is not defined or peer_conf.connection_type == 'initiate' %} - auto = start - keyingtries = %forever -{% elif peer_conf.connection_type == 'respond' %} - auto = route - keyingtries = 1 -{% endif %} -{% endif %} -{% if tunnel_conf.passthrough is defined and tunnel_conf.passthrough is not none %} -conn peer-{{ peer }}-tunnel-{{ tunnel_id }}-passthough - left = {{ peer_conf.local_address if peer_conf.local_address != 'any' else '%defaultroute' }} - right = {{ peer if peer not in ['any', '0.0.0.0'] and peer[0:1] != '@' else '%any' }} - leftsubnet = {{ tunnel_conf.local.prefix }} - rightsubnet = {{ tunnel_conf.local.prefix }} - type = passthrough - authby = never - auto = route -{% endif %} -{% endfor %} -{% endif %} -{% endfor %} -{% endif %} - {% if include_ipsec_conf is defined %} include {{ include_ipsec_conf }} {% endif %} diff --git a/data/templates/ipsec/ipsec.secrets.tmpl b/data/templates/ipsec/ipsec.secrets.tmpl index a1432de57..43b5fe0d2 100644 --- a/data/templates/ipsec/ipsec.secrets.tmpl +++ b/data/templates/ipsec/ipsec.secrets.tmpl @@ -1,24 +1,5 @@ # Created by VyOS - manual changes will be overwritten -{% if site_to_site is defined and "peer" in site_to_site %} -{% set ns = namespace(local_key_set=False) %} -{% for peer, peer_conf in site_to_site.peer.items() %} -{% if peer_conf.authentication.mode == 'pre-shared-secret' %} -{{ (peer_conf.local_address if "local_address" in peer_conf else "%any") ~ - (" " ~ peer) ~ - ((" " ~ peer_conf.authentication.id) if "id" in peer_conf.authentication else "") ~ - ((" " ~ peer_conf.authentication.remote_id) if "remote_id" in peer_conf.authentication else "") -}} : PSK "{{ peer_conf.authentication.pre_shared_secret }}" # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }} -{% elif peer_conf.authentication.mode == 'x509' %} -{% set key_file = peer_conf.authentication.x509.key.file %} -: RSA {{ key_file if key_file.startswith(x509_path) else (x509_path + key_file) }}{% if "password" in peer_conf.authentication.x509.key and peer_conf.authentication.x509.key.password %} "{{ peer_conf.authentication.x509.key.password}}"{% endif %} -{% elif peer_conf.authentication.mode == 'rsa' and not ns.local_key_set %} -{% set ns.local_key_set = True %} -: RSA {{ rsa_local_key }} -{% endif %} -{% endfor %} -{% endif %} - {% if include_ipsec_secrets is defined %} include {{ include_ipsec_secrets }} {% endif %} diff --git a/data/templates/ipsec/swanctl.conf.tmpl b/data/templates/ipsec/swanctl.conf.tmpl index 0ce703f20..ea6d85743 100644 --- a/data/templates/ipsec/swanctl.conf.tmpl +++ b/data/templates/ipsec/swanctl.conf.tmpl @@ -1,54 +1,72 @@ # Created by VyOS - manual changes will be overwritten -{% if profile is defined %} +{% import 'ipsec/swanctl/profile.tmpl' as profile_tmpl %} +{% import 'ipsec/swanctl/peer.tmpl' as peer_tmpl %} + +{% if profile is defined or site_to_site is defined %} connections { -{% for name, profile_conf in profile.items() if "bind" in profile_conf and "tunnel" in profile_conf.bind %} -{% set dmvpn_ike = ike_group[profile_conf.ike_group] %} -{% set dmvpn_esp = esp_group[profile_conf.esp_group] %} -{% for interface in profile_conf.bind.tunnel %} - dmvpn-{{ name }}-{{ interface }} { - proposals = {{ ciphers.ike[profile_conf.ike_group][:-1] }} - version = {{ dmvpn_ike.key_exchange[4:] if "key_exchange" in dmvpn_ike else "0" }} - rekey_time = {{ dmvpn_ike.lifetime if 'lifetime' in dmvpn_ike else '28800' }}s - keyingtries = 0 -{% if profile_conf.authentication.mode == 'pre-shared-secret' %} - local { - auth = psk - } - remote { - auth = psk - } -{% endif %} - children { - dmvpn { - esp_proposals = {{ ciphers.esp[profile_conf.esp_group][:-1] }} - rekey_time = {{ dmvpn_esp.lifetime if 'lifetime' in dmvpn_esp else '3600' }}s - rand_time = 540s - local_ts = dynamic[gre] - remote_ts = dynamic[gre] - mode = {{ dmvpn_esp.mode if "mode" in dmvpn_esp else "transport" }} -{% if 'dead_peer_detection' in dmvpn_ike and 'action' in dmvpn_ike.dead_peer_detection %} - dpd_action = {{ dmvpn_ike.dead_peer_detection.action }} -{% endif %} -{% if 'compression' in dmvpn_esp and dmvpn_esp['compression'] == 'enable' %} - ipcomp = yes -{% endif %} - } - } - } -{% endfor %} -{% endfor %} +{% if profile is defined %} +{% for name, profile_conf in profile.items() if profile_conf.disable is not defined and profile_conf.bind is defined and profile_conf.bind.tunnel is defined %} +{% set dmvpn_ike = ike_group[profile_conf.ike_group] %} +{% set dmvpn_esp = esp_group[profile_conf.esp_group] %} +{{ profile_tmpl.conn(name, profile_conf, dmvpn_ike, dmvpn_esp, ciphers) }} +{% endfor %} +{% endif %} +{% if site_to_site is defined and site_to_site.peer is defined %} +{% for peer, peer_conf in site_to_site.peer.items() if peer not in dhcp_no_address and peer_conf.disable is not defined %} +{% set peer_conn_name = peer.replace(".", "-").replace("@", "") %} +{% set peer_ike = ike_group[peer_conf.ike_group] %} +{% set peer_esp = esp_group[peer_conf.default_esp_group] if peer_conf.default_esp_group is defined else None %} +{% set auth_type = authby[peer_conf.authentication.mode] %} +{{ peer_tmpl.conn(peer_conn_name, peer, peer_conf, peer_ike, peer_esp, ciphers, esp_group, auth_type, marks) }} +{% endfor %} +{% endif %} } secrets { -{% for name, profile_conf in profile.items() if "bind" in profile_conf and "tunnel" in profile_conf.bind %} -{% if profile_conf.authentication.mode == 'pre-shared-secret' %} -{% for interface in profile_conf.bind.tunnel %} +{% if profile is defined %} +{% for name, profile_conf in profile.items() if profile_conf.disable is not defined and profile_conf.bind is defined and profile_conf.bind.tunnel is defined %} +{% if profile_conf.authentication.mode == 'pre-shared-secret' %} +{% for interface in profile_conf.bind.tunnel %} ike-dmvpn-{{ interface }} { secret = {{ profile_conf.authentication.pre_shared_secret }} } -{% endfor %} -{% endif %} -{% endfor %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} +{% if site_to_site is defined and site_to_site.peer is defined %} +{% set ns = namespace(local_key_set=False) %} +{% for peer, peer_conf in site_to_site.peer.items() if peer not in dhcp_no_address and peer_conf.disable is not defined %} +{% set peer_conn_name = peer.replace(".", "-").replace("@", "") %} +{% if peer_conf.authentication.mode == 'pre-shared-secret' %} + ike_{{ peer_conn_name }} { +{% if peer_conf.local_address is defined %} + id-local = {{ peer_conf.local_address }} # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }} +{% endif %} + id-remote = {{ peer }} +{% if peer_conf.authentication.id is defined %} + id-localid = {{ peer_conf.authentication.id }} +{% endif %} +{% if peer_conf.authentication.remote_id is defined %} + id-remoteid = {{ peer_conf.authentication.remote_id }} +{% endif %} + secret = "{{ peer_conf.authentication.pre_shared_secret }}" + } +{% elif peer_conf.authentication.mode == 'x509' %} + private_{{ peer_conn_name }} { + file = {{ peer_conf.authentication.x509.key.file }} +{% if "password" in peer_conf.authentication.x509.key and peer_conf.authentication.x509.key.password %} + secret = "{{ peer_conf.authentication.x509.key.password}}" +{% endif %} + } +{% elif peer_conf.authentication.mode == 'rsa' and not ns.local_key_set %} +{% set ns.local_key_set = True %} + rsa_local { + file = {{ rsa_local_key }} + } +{% endif %} +{% endfor %} +{% endif %} } -{% endif %} +{% endif %} diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl new file mode 100644 index 000000000..0d01cd546 --- /dev/null +++ b/data/templates/ipsec/swanctl/peer.tmpl @@ -0,0 +1,136 @@ +{% macro conn(name, peer, peer_conf, ike, esp, ciphers, esp_group, auth_type, marks) %} + peer_{{ name }} { + proposals = {{ ciphers.ike[peer_conf.ike_group] }} + version = {{ ike['key_exchange'][4:] if "key_exchange" in ike else "0" }} + local_addrs = {{ peer_conf.local_address if peer_conf.local_address != 'any' else '0.0.0.0/0' }} # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }} + remote_addrs = {{ peer if peer not in ['any', '0.0.0.0'] and peer[0:1] != '@' else '0.0.0.0/0' }} +{% if peer_conf.authentication.mode == 'x509' %} + send_cert = always +{% endif %} +{% if "dead_peer_detection" in ike %} + dpd_timeout = {{ ike.dead_peer_detection.timeout }} + dpd_delay = {{ ike.dead_peer_detection.interval }} +{% endif %} +{% if "key_exchange" in ike and ike.key_exchange == "ikev1" and "mode" in ike and ike.mode == "aggressive" %} + aggressive = yes +{% endif %} + mobike = {{ "yes" if "mobike" not in ike or ike.mobike == "enable" else "no" }} +{% if peer[0:1] == '@' %} + keyingtries = 0 + rekey_time = 0 + reauth_time = 0 +{% elif peer_conf.connection_type is not defined or peer_conf.connection_type == 'initiate' %} + keyingtries = 0 +{% elif peer_conf.connection_type is defined and peer_conf.connection_type == 'respond' %} + keyingtries = 1 +{% endif %} +{% if peer_conf.force_encapsulation is defined and peer_conf.force_encapsulation == 'enable' %} + encap = yes +{% endif %} + local { +{% if peer_conf.authentication.id is defined and peer_conf.authentication.use_x509_id is not defined %} + id = "{{ peer_conf.authentication.id }}" +{% endif %} +{% if auth_type %} + auth = {{ auth_type }} +{% endif %} +{% if peer_conf.authentication.mode == 'x509' %} + certs = {{ peer_conf.authentication.x509.cert_file }} +{% elif peer_conf.authentication.mode == 'rsa' %} + pubkeys = localhost.pub +{% endif %} + } + remote { +{% if peer_conf.authentication.remote_id is defined %} + id = "{{ peer_conf.authentication.remote_id }}" +{% elif peer[0:1] == '@' %} + id = "{{ peer }}" +{% endif %} +{% if auth_type %} + auth = {{ auth_type }} +{% endif %} +{% if peer_conf.authentication.mode == 'rsa' %} + pubkeys = {{ peer_conf.authentication.rsa_key_name }}.pub +{% endif %} + } + children { +{% if peer_conf.vti is defined and peer_conf.vti.bind is defined and peer_conf.tunnel is not defined %} +{% set vti_esp = esp_group[peer_conf.vti.esp_group] if peer_conf.vti.esp_group is defined else None %} + peer_{{ name }}_vti { + esp_proposals = {{ ciphers.esp[peer_conf.vti.esp_group] }} + local_ts = 0.0.0.0/0,::/0 + remote_ts = 0.0.0.0/0,::/0 + updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }} {{ peer_conf.dhcp_interface if peer_conf.dhcp_interface is defined else 'no' }}" + mark_in = {{ marks[peer_conf.vti.bind] }} + mark_out = {{ marks[peer_conf.vti.bind] }} + ipcomp = {{ 'yes' if "compression" in vti_esp and vti_esp.compression == 'enable' else 'no' }} + mode = {{ vti_esp.mode if "mode" in vti_esp else "tunnel" }} +{% if peer[0:1] == '@' %} + start_action = none +{% elif peer_conf.connection_type is not defined or peer_conf.connection_type == 'initiate' %} + start_action = start +{% elif peer_conf.connection_type == 'respond' %} + start_action = trap +{% endif %} +{% if "dead_peer_detection" in ike %} +{% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'start'} %} + dpd_action = {{ dpd_translate[ike.dead_peer_detection.action] }} +{% endif %} + } +{% endif %} +{% if peer_conf.tunnel is defined %} +{% for tunnel_id, tunnel_conf in peer_conf.tunnel.items() if tunnel_conf.disable is not defined %} +{% set tunnel_esp_name = tunnel_conf.esp_group if "esp_group" in tunnel_conf else peer_conf.default_esp_group %} +{% set tunnel_esp = esp_group[tunnel_esp_name] %} +{% set proto = tunnel_conf.protocol if "protocol" in tunnel_conf else '' %} +{% set local_port = tunnel_conf.local.port if tunnel_conf.local is defined and tunnel_conf.local.port is defined else '' %} +{% set local_suffix = '[{0}/{1}]'.format(proto, local_port) if proto or local_port else '' %} +{% set remote_port = tunnel_conf.remote.port if tunnel_conf.remote is defined and tunnel_conf.remote.port is defined else '' %} +{% set remote_suffix = '[{0}/{1}]'.format(proto, remote_port) if proto or remote_port else '' %} + peer_{{ name }}_tunnel_{{ tunnel_id }} { + esp_proposals = {{ ciphers.esp[tunnel_esp_name] }} +{% if tunnel_esp.mode is not defined or tunnel_esp.mode == 'tunnel' %} +{% if tunnel_conf.local is defined and tunnel_conf.local.prefix is defined %} +{% set local_prefix = tunnel_conf.local.prefix if 'any' not in tunnel_conf.local.prefix else ['0.0.0.0/0', '::/0'] %} + local_ts = {{ local_prefix | join(local_suffix + ",") }}{{ local_suffix }} +{% endif %} +{% if tunnel_conf.remote is defined and tunnel_conf.remote.prefix is defined %} +{% set remote_prefix = tunnel_conf.remote.prefix if 'any' not in tunnel_conf.remote.prefix else ['0.0.0.0/0', '::/0'] %} + remote_ts = {{ remote_prefix | join(remote_suffix + ",") }}{{ remote_suffix }} +{% endif %} +{% elif tunnel_esp.mode == 'transport' %} + local_ts = {{ peer_conf.local_address }}{{ local_suffix }} + remote_ts = {{ peer }}{{ remote_suffix }} +{% endif %} + ipcomp = {{ 'yes' if "compression" in tunnel_esp and tunnel_esp.compression == 'enable' else 'no' }} + mode = {{ tunnel_esp.mode if "mode" in tunnel_esp else "tunnel" }} +{% if peer[0:1] == '@' %} + start_action = none +{% elif peer_conf.connection_type is not defined or peer_conf.connection_type == 'initiate' %} + start_action = start +{% elif peer_conf.connection_type == 'respond' %} + start_action = trap +{% endif %} +{% if "dead_peer_detection" in ike %} +{% set dpd_translate = {'clear': 'clear', 'hold': 'trap', 'restart': 'start'} %} + dpd_action = {{ dpd_translate[ike.dead_peer_detection.action] }} +{% endif %} +{% if peer_conf.vti is defined and peer_conf.vti.bind is defined %} + updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }} {{ peer_conf.dhcp_interface if peer_conf.dhcp_interface is defined else 'no' }}" + mark_in = {{ marks[peer_conf.vti.bind] }} + mark_out = {{ marks[peer_conf.vti.bind] }} +{% endif %} + } +{% if tunnel_conf.passthrough is defined and tunnel_conf.passthrough %} + peer_{{ name }}_tunnel_{{ tunnel_id }}_passthough { + local_ts = {{ tunnel_conf.passthrough | join(",") }} + remote_ts = {{ tunnel_conf.passthrough | join(",") }} + start_action = trap + mode = pass + } +{% endif %} +{% endfor %} +{% endif %} + } + } +{% endmacro %} diff --git a/data/templates/ipsec/swanctl/profile.tmpl b/data/templates/ipsec/swanctl/profile.tmpl new file mode 100644 index 000000000..e4b36b99f --- /dev/null +++ b/data/templates/ipsec/swanctl/profile.tmpl @@ -0,0 +1,34 @@ +{% macro conn(name, profile_conf, ike, esp, ciphers) %} +{% for interface in profile_conf.bind.tunnel %} + dmvpn-{{ name }}-{{ interface }} { + proposals = {{ ciphers.ike[profile_conf.ike_group] }} + version = {{ ike.key_exchange[4:] if "key_exchange" in ike else "0" }} + rekey_time = {{ ike.lifetime if 'lifetime' in ike else '28800' }}s + keyingtries = 0 +{% if profile_conf.authentication.mode == 'pre-shared-secret' %} + local { + auth = psk + } + remote { + auth = psk + } +{% endif %} + children { + dmvpn { + esp_proposals = {{ ciphers.esp[profile_conf.esp_group] }} + rekey_time = {{ esp.lifetime if 'lifetime' in esp else '3600' }}s + rand_time = 540s + local_ts = dynamic[gre] + remote_ts = dynamic[gre] + mode = {{ esp.mode if "mode" in esp else "transport" }} +{% if 'dead_peer_detection' in ike and 'action' in ike.dead_peer_detection %} + dpd_action = {{ ike.dead_peer_detection.action }} +{% endif %} +{% if 'compression' in esp and esp['compression'] == 'enable' %} + ipcomp = yes +{% endif %} + } + } + } +{% endfor %} +{% endmacro %} diff --git a/data/templates/snmp/override.conf.tmpl b/data/templates/snmp/override.conf.tmpl index 68f5fd931..90c294ed0 100644 --- a/data/templates/snmp/override.conf.tmpl +++ b/data/templates/snmp/override.conf.tmpl @@ -5,7 +5,7 @@ After=vyos-router.service [Service] Environment= -Environment="MIBSDIR=/usr/share/snmp/mibs:/usr/share/snmp/mibs/iana:/usr/share/snmp/mibs/ietf:/usr/share/mibs/site:/usr/share/snmp/mibs:/usr/share/mibs/iana:/usr/share/mibs/ietf:/usr/share/mibs/netsnmp" +Environment="MIBDIRS=/usr/share/snmp/mibs:/usr/share/snmp/mibs/iana:/usr/share/snmp/mibs/ietf:/usr/share/vyos/mibs" ExecStart= ExecStart={{vrf_command}}/usr/sbin/snmpd -LS0-5d -Lf /dev/null -u Debian-snmp -g Debian-snmp -I -ipCidrRouteTable,inetCidrRouteTable -f -p /run/snmpd.pid Restart=always diff --git a/data/templates/wwan/chat.tmpl b/data/templates/wwan/chat.tmpl deleted file mode 100644 index 386af37e6..000000000 --- a/data/templates/wwan/chat.tmpl +++ /dev/null @@ -1,10 +0,0 @@ -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/ip-down.script.tmpl b/data/templates/wwan/ip-down.script.tmpl deleted file mode 100644 index 9dc15ea99..000000000 --- a/data/templates/wwan/ip-down.script.tmpl +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -# Script parameters will be like: -# wlm0 /dev/serial/by-bus/usb0b1.3p1.3 115200 10.100.118.91 10.64.64.64 wlm0 - -# Only applicable for Wireless Modems (WWAN) -if [ -z $(echo $2 | egrep "(ttyS[0-9]+|usb[0-9]+b.*)$") ]; then - exit 0 -fi - -# Determine if we are running inside a VRF or not, required for proper routing table -# NOTE: the down script can not be properly templated as we need the VRF name, -# which is not present on deletion, thus we read it from the operating system. -if [ -d /sys/class/net/{{ ifname }}/upper_* ]; then - # Determine upper (VRF) interface - VRF=$(basename $(ls -d /sys/class/net/{{ ifname }}/upper_*)) - # Remove upper_ prefix from result string - VRF_NAME=${VRF#"upper_"} - # Remove default route from VRF routing table - vtysh -c "conf t" -c "vrf ${VRF_NAME}" -c "no ip route 0.0.0.0/0 {{ ifname }}" -else - # Remove default route from GRT (global routing table) - vtysh -c "conf t" -c "no ip route 0.0.0.0/0 {{ ifname }}" -fi - -DIALER_PID=$(cat /var/run/{{ ifname }}.pid) -logger -t pppd[$DIALER_PID] "removed default route via {{ ifname }} metric {{ backup.distance }}" diff --git a/data/templates/wwan/ip-pre-up.script.tmpl b/data/templates/wwan/ip-pre-up.script.tmpl deleted file mode 100644 index 199150947..000000000 --- a/data/templates/wwan/ip-pre-up.script.tmpl +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -# As WWAN is an "on demand" interface we need to re-configure it when it -# becomes 'up' - -ipparam=$6 - -# device name and metric are received using ipparam -device=`echo "$ipparam"|awk '{ print $1 }'` - -if [ "$device" != "{{ ifname }}" ]; then - exit -fi - -# add some info to syslog -DIALER_PID=$(cat /var/run/{{ ifname }}.pid) -logger -t pppd[$DIALER_PID] "executing $0" - -echo "{{ description }}" > /sys/class/net/{{ ifname }}/ifalias - -{% if vrf %} -logger -t pppd[$DIALER_PID] "configuring interface {{ ifname }} for VRF {{ vrf }}" -ip link set dev {{ ifname }} master {{ vrf }} -{% endif %} diff --git a/data/templates/wwan/ip-up.script.tmpl b/data/templates/wwan/ip-up.script.tmpl deleted file mode 100644 index 2603a0286..000000000 --- a/data/templates/wwan/ip-up.script.tmpl +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -# Script parameters will be like: -# wlm0 /dev/serial/by-bus/usb0b1.3p1.3 115200 10.100.118.91 10.64.64.64 wlm0 - -# Only applicable for Wireless Modems (WWAN) -if [ -z $(echo $2 | egrep "(ttyS[0-9]+|usb[0-9]+b.*)$") ]; then - exit 0 -fi - -# Determine if we are running inside a VRF or not, required for proper routing table -if [ -d /sys/class/net/{{ ifname }}/upper_* ]; then - # Determine upper (VRF) interface - VRF=$(basename $(ls -d /sys/class/net/{{ ifname }}/upper_*)) - # Remove upper_ prefix from result string - VRF_NAME=${VRF#"upper_"} - # Remove default route from VRF routing table - vtysh -c "conf t" -c "vrf ${VRF_NAME}" -c "ip route 0.0.0.0/0 {{ ifname }} {{ backup.distance }}" -else - # Remove default route from GRT (global routing table) - vtysh -c "conf t" -c "ip route 0.0.0.0/0 {{ ifname }} {{ backup.distance }}" -fi - -DIALER_PID=$(cat /var/run/{{ ifname }}.pid) -logger -t pppd[$DIALER_PID] "added default route via {{ ifname }} metric {{ backup.distance }} ${VRF_NAME}" diff --git a/data/templates/wwan/peer.tmpl b/data/templates/wwan/peer.tmpl deleted file mode 100644 index 2807a79a4..000000000 --- a/data/templates/wwan/peer.tmpl +++ /dev/null @@ -1,31 +0,0 @@ -### Autogenerated by interfaces-wirelessmodem.py ### - -{{ "# description: " + description if description is defined }} -ifname {{ ifname }} -ipparam {{ ifname }} -linkname {{ ifname }} - -{{ "usepeerdns" if no_peer_dns is defined }} -# physical device -{{ device }} -lcp-echo-failure 0 -115200 -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 -ipcp-accept-remote -noauth -crtscts -lock -persist -{{ "demand" if connect_on_demand is defined }} - -connect '/usr/sbin/chat -v -t6 -f /etc/ppp/peers/chat.{{ ifname }}' - diff --git a/debian/compat b/debian/compat index ec635144f..f599e28b8 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -9 +10 diff --git a/debian/control b/debian/control index ab3b46ba1..22fb86d49 100644 --- a/debian/control +++ b/debian/control @@ -74,12 +74,14 @@ Depends: libstrongswan-standard-plugins (>=5.8), libstrongswan-extra-plugins (>=5.8), libcharon-extra-plugins (>=5.8), + libqmi-utils, libvyosconfig0, lldpd, lm-sensors, lsscsi, mdns-repeater, minisign, + modemmanager, mtr-tiny, netplug, nftables (>= 0.9.3), @@ -102,7 +104,7 @@ Depends: procps, python3, python3-certbot-nginx, - python3-crypto, + python3-crypt | python3-pycryptodome, ${python3:Depends}, python3-flask, python3-hurry.filesize, diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index 0c6c226ee..7d12f3bc1 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -1,3 +1,4 @@ +etc/cron.hourly etc/dhcp etc/ipsec.d etc/netplug diff --git a/debian/vyos-1x.links b/debian/vyos-1x.links deleted file mode 100644 index ea4e4e7da..000000000 --- a/debian/vyos-1x.links +++ /dev/null @@ -1 +0,0 @@ -/usr/share/vyos/mibs /usr/share/snmp/mibs diff --git a/interface-definitions/cron.xml.in b/interface-definitions/cron.xml.in index ad2cb36ad..58dcf64ac 100644 --- a/interface-definitions/cron.xml.in +++ b/interface-definitions/cron.xml.in @@ -1,7 +1,4 @@ <?xml version="1.0"?> - -<!-- Cron configuration --> - <interfaceDefinition> <node name="system"> <children> diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 78a4fb763..37fc7259f 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1114,6 +1114,29 @@ </leafNode> </children> </node> + <leafNode name="bandwidth"> + <properties> + <help>Link Bandwidth attribute</help> + <completionHelp> + <list>default-weight-for-missing ignore skip-missing</list> + </completionHelp> + <valueHelp> + <format>default-weight-for-missing</format> + <description>Assign low default weight (1) to paths not having link bandwidth</description> + </valueHelp> + <valueHelp> + <format>ignore</format> + <description>Ignore link bandwidth (do regular ECMP, not weighted)</description> + </valueHelp> + <valueHelp> + <format>skip-missing</format> + <description>Ignore paths without link bandwidth for ECMP (if other paths have it)</description> + </valueHelp> + <constraint> + <regex>^(default-weight-for-missing|ignore|skip-missing)$</regex> + </constraint> + </properties> + </leafNode> <leafNode name="compare-routerid"> <properties> <help>Compare the router-id for identical EBGP paths</help> diff --git a/interface-definitions/include/interface/authentication.xml.i b/interface-definitions/include/interface/authentication.xml.i new file mode 100644 index 000000000..c097ca9dd --- /dev/null +++ b/interface-definitions/include/interface/authentication.xml.i @@ -0,0 +1,27 @@ +<!-- include start from interface/authentication.xml.i --> +<node name="authentication"> + <properties> + <help>Authentication settings</help> + </properties> + <children> + <leafNode name="user"> + <properties> + <help>User name</help> + <valueHelp> + <format>txt</format> + <description>Username used for connection</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="password"> + <properties> + <help>Password</help> + <valueHelp> + <format>txt</format> + <description>Password used for connection</description> + </valueHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ssh-group.xml.i b/interface-definitions/include/ssh-group.xml.i new file mode 100644 index 000000000..9c8b8692f --- /dev/null +++ b/interface-definitions/include/ssh-group.xml.i @@ -0,0 +1,12 @@ +<!-- include start from ssh-group.xml.i --> +<leafNode name="group"> + <properties> + <help>Allow members of a group to login</help> + <constraint> + <regex>[a-z_][a-z0-9_-]{1,31}[$]?</regex> + </constraint> + <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/ssh-user.xml.i b/interface-definitions/include/ssh-user.xml.i new file mode 100644 index 000000000..677602dd8 --- /dev/null +++ b/interface-definitions/include/ssh-user.xml.i @@ -0,0 +1,12 @@ +<!-- include start from ssh-user.xml.i --> +<leafNode name="user"> + <properties> + <help>Allow specific users to login</help> + <constraint> + <regex>[a-z_][a-z0-9_-]{1,31}[$]?</regex> + </constraint> + <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/vpn-ipsec-encryption.xml.i b/interface-definitions/include/vpn-ipsec-encryption.xml.i index 041ba9902..9ef2f7c90 100644 --- a/interface-definitions/include/vpn-ipsec-encryption.xml.i +++ b/interface-definitions/include/vpn-ipsec-encryption.xml.i @@ -1,233 +1,233 @@ <!-- include start from vpn-ipsec-encryption.xml.i --> - <leafNode name="encryption"> - <properties> - <help>Encryption algorithm</help> - <completionHelp> - <list>null aes128 aes192 aes256 aes128ctr aes192ctr aes256ctr aes128ccm64 aes192ccm64 aes256ccm64 aes128ccm96 aes192ccm96 aes256ccm96 aes128ccm128 aes192ccm128 aes256ccm128 aes128gcm64 aes192gcm64 aes256gcm64 aes128gcm96 aes192gcm96 aes256gcm96 aes128gcm128 aes192gcm128 aes256gcm128 aes128gmac aes192gmac aes256gmac 3des blowfish128 blowfish192 blowfish256 camellia128 camellia192 camellia256 camellia128ctr camellia192ctr camellia256ctr camellia128ccm64 camellia192ccm64 camellia256ccm64 camellia128ccm96 camellia192ccm96 camellia256ccm96 camellia128ccm128 camellia192ccm128 camellia256ccm128 serpent128 serpent192 serpent256 twofish128 twofish192 twofish256 cast128 chacha20poly1305</list> - </completionHelp> - <valueHelp> - <format>null</format> - <description>Null encryption</description> - </valueHelp> - <valueHelp> - <format>aes128</format> - <description>128 bit AES-CBC (default)</description> - </valueHelp> - <valueHelp> - <format>aes192</format> - <description>192 bit AES-CBC</description> - </valueHelp> - <valueHelp> - <format>aes256</format> - <description>256 bit AES-CBC</description> - </valueHelp> - <valueHelp> - <format>aes128ctr</format> - <description>128 bit AES-COUNTER</description> - </valueHelp> - <valueHelp> - <format>aes192ctr</format> - <description>192 bit AES-COUNTER</description> - </valueHelp> - <valueHelp> - <format>aes256ctr</format> - <description>256 bit AES-COUNTER</description> - </valueHelp> - <valueHelp> - <format>aes128ccm64</format> - <description>128 bit AES-CCM with 64 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes192ccm64</format> - <description>192 bit AES-CCM with 64 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes256ccm64</format> - <description>256 bit AES-CCM with 64 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes128ccm96</format> - <description>128 bit AES-CCM with 96 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes192ccm96</format> - <description>192 bit AES-CCM with 96 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes256ccm96</format> - <description>256 bit AES-CCM with 96 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes128ccm128</format> - <description>128 bit AES-CCM with 128 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes192ccm128</format> - <description>192 bit AES-CCM with 128 bit IC</description> - </valueHelp> - <valueHelp> - <format>aes256ccm128</format> - <description>256 bit AES-CCM with 128 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes128gcm64</format> - <description>128 bit AES-GCM with 64 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes192gcm64</format> - <description>192 bit AES-GCM with 64 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes256gcm64</format> - <description>256 bit AES-GCM with 64 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes128gcm96</format> - <description>128 bit AES-GCM with 96 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes192gcm96</format> - <description>192 bit AES-GCM with 96 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes256gcm96</format> - <description>256 bit AES-GCM with 96 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes128gcm128</format> - <description>128 bit AES-GCM with 128 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes192gcm128</format> - <description>192 bit AES-GCM with 128 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes256gcm128</format> - <description>256 bit AES-GCM with 128 bit ICV</description> - </valueHelp> - <valueHelp> - <format>aes128gmac</format> - <description>Null encryption with 128 bit AES-GMAC</description> - </valueHelp> - <valueHelp> - <format>aes192gmac</format> - <description>Null encryption with 192 bit AES-GMAC</description> - </valueHelp> - <valueHelp> - <format>aes256gmac</format> - <description>Null encryption with 256 bit AES-GMAC</description> - </valueHelp> - <valueHelp> - <format>3des</format> - <description>168 bit 3DES-EDE-CBC</description> - </valueHelp> - <valueHelp> - <format>blowfish128</format> - <description>128 bit Blowfish-CBC</description> - </valueHelp> - <valueHelp> - <format>blowfish192</format> - <description>192 bit Blowfish-CBC</description> - </valueHelp> - <valueHelp> - <format>blowfish256</format> - <description>256 bit Blowfish-CBC</description> - </valueHelp> - <valueHelp> - <format>camellia128</format> - <description>128 bit Camellia-CBC</description> - </valueHelp> - <valueHelp> - <format>camellia192</format> - <description>192 bit Camellia-CBC</description> - </valueHelp> - <valueHelp> - <format>camellia256</format> - <description>256 bit Camellia-CBC</description> - </valueHelp> - <valueHelp> - <format>camellia128ctr</format> - <description>128 bit Camellia-COUNTER</description> - </valueHelp> - <valueHelp> - <format>camellia192ctr</format> - <description>192 bit Camellia-COUNTER</description> - </valueHelp> - <valueHelp> - <format>camellia256ctr</format> - <description>256 bit Camellia-COUNTER</description> - </valueHelp> - <valueHelp> - <format>camellia128ccm64</format> - <description>128 bit Camellia-CCM with 64 bit ICV</description> - </valueHelp> - <valueHelp> - <format>camellia192ccm64</format> - <description>192 bit Camellia-CCM with 64 bit ICV</description> - </valueHelp> - <valueHelp> - <format>camellia256ccm64</format> - <description>256 bit Camellia-CCM with 64 bit ICV</description> - </valueHelp> - <valueHelp> - <format>camellia128ccm96</format> - <description>128 bit Camellia-CCM with 96 bit ICV</description> - </valueHelp> - <valueHelp> - <format>camellia192ccm96</format> - <description>192 bit Camellia-CCM with 96 bit ICV</description> - </valueHelp> - <valueHelp> - <format>camellia256ccm96</format> - <description>256 bit Camellia-CCM with 96 bit ICV</description> - </valueHelp> - <valueHelp> - <format>camellia128ccm128</format> - <description>128 bit Camellia-CCM with 128 bit ICV</description> - </valueHelp> - <valueHelp> - <format>camellia192ccm128</format> - <description>192 bit Camellia-CCM with 128 bit ICV</description> - </valueHelp> - <valueHelp> - <format>camellia256ccm128</format> - <description>256 bit Camellia-CCM with 128 bit ICV</description> - </valueHelp> - <valueHelp> - <format>serpent128</format> - <description>128 bit Serpent-CBC</description> - </valueHelp> - <valueHelp> - <format>serpent192</format> - <description>192 bit Serpent-CBC</description> - </valueHelp> - <valueHelp> - <format>serpent256</format> - <description>256 bit Serpent-CBC</description> - </valueHelp> - <valueHelp> - <format>twofish128</format> - <description>128 bit Twofish-CBC</description> - </valueHelp> - <valueHelp> - <format>twofish192</format> - <description>192 bit Twofish-CBC</description> - </valueHelp> - <valueHelp> - <format>twofish256</format> - <description>256 bit Twofish-CBC</description> - </valueHelp> - <valueHelp> - <format>cast128</format> - <description>128 bit CAST-CBC</description> - </valueHelp> - <valueHelp> - <format>chacha20poly1305</format> - <description>256 bit ChaCha20/Poly1305 with 128 bit ICV</description> - </valueHelp> - <constraint> - <regex>^(null|aes128|aes192|aes256|aes128ctr|aes192ctr|aes256ctr|aes128ccm64|aes192ccm64|aes256ccm64|aes128ccm96|aes192ccm96|aes256ccm96|aes128ccm128|aes192ccm128|aes256ccm128|aes128gcm64|aes192gcm64|aes256gcm64|aes128gcm96|aes192gcm96|aes256gcm96|aes128gcm128|aes192gcm128|aes256gcm128|aes128gmac|aes192gmac|aes256gmac|3des|blowfish128|blowfish192|blowfish256|camellia128|camellia192|camellia256|camellia128ctr|camellia192ctr|camellia256ctr|camellia128ccm64|camellia192ccm64|camellia256ccm64|camellia128ccm96|camellia192ccm96|camellia256ccm96|camellia128ccm128|camellia192ccm128|camellia256ccm128|serpent128|serpent192|serpent256|twofish128|twofish192|twofish256|cast128|chacha20poly1305)$</regex> - </constraint> - </properties> - </leafNode> +<leafNode name="encryption"> + <properties> + <help>Encryption algorithm</help> + <completionHelp> + <list>null aes128 aes192 aes256 aes128ctr aes192ctr aes256ctr aes128ccm64 aes192ccm64 aes256ccm64 aes128ccm96 aes192ccm96 aes256ccm96 aes128ccm128 aes192ccm128 aes256ccm128 aes128gcm64 aes192gcm64 aes256gcm64 aes128gcm96 aes192gcm96 aes256gcm96 aes128gcm128 aes192gcm128 aes256gcm128 aes128gmac aes192gmac aes256gmac 3des blowfish128 blowfish192 blowfish256 camellia128 camellia192 camellia256 camellia128ctr camellia192ctr camellia256ctr camellia128ccm64 camellia192ccm64 camellia256ccm64 camellia128ccm96 camellia192ccm96 camellia256ccm96 camellia128ccm128 camellia192ccm128 camellia256ccm128 serpent128 serpent192 serpent256 twofish128 twofish192 twofish256 cast128 chacha20poly1305</list> + </completionHelp> + <valueHelp> + <format>null</format> + <description>Null encryption</description> + </valueHelp> + <valueHelp> + <format>aes128</format> + <description>128 bit AES-CBC (default)</description> + </valueHelp> + <valueHelp> + <format>aes192</format> + <description>192 bit AES-CBC</description> + </valueHelp> + <valueHelp> + <format>aes256</format> + <description>256 bit AES-CBC</description> + </valueHelp> + <valueHelp> + <format>aes128ctr</format> + <description>128 bit AES-COUNTER</description> + </valueHelp> + <valueHelp> + <format>aes192ctr</format> + <description>192 bit AES-COUNTER</description> + </valueHelp> + <valueHelp> + <format>aes256ctr</format> + <description>256 bit AES-COUNTER</description> + </valueHelp> + <valueHelp> + <format>aes128ccm64</format> + <description>128 bit AES-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192ccm64</format> + <description>192 bit AES-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes256ccm64</format> + <description>256 bit AES-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128ccm96</format> + <description>128 bit AES-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192ccm96</format> + <description>192 bit AES-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes256ccm96</format> + <description>256 bit AES-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128ccm128</format> + <description>128 bit AES-CCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192ccm128</format> + <description>192 bit AES-CCM with 128 bit IC</description> + </valueHelp> + <valueHelp> + <format>aes256ccm128</format> + <description>256 bit AES-CCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128gcm64</format> + <description>128 bit AES-GCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192gcm64</format> + <description>192 bit AES-GCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes256gcm64</format> + <description>256 bit AES-GCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128gcm96</format> + <description>128 bit AES-GCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192gcm96</format> + <description>192 bit AES-GCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes256gcm96</format> + <description>256 bit AES-GCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128gcm128</format> + <description>128 bit AES-GCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes192gcm128</format> + <description>192 bit AES-GCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes256gcm128</format> + <description>256 bit AES-GCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>aes128gmac</format> + <description>Null encryption with 128 bit AES-GMAC</description> + </valueHelp> + <valueHelp> + <format>aes192gmac</format> + <description>Null encryption with 192 bit AES-GMAC</description> + </valueHelp> + <valueHelp> + <format>aes256gmac</format> + <description>Null encryption with 256 bit AES-GMAC</description> + </valueHelp> + <valueHelp> + <format>3des</format> + <description>168 bit 3DES-EDE-CBC</description> + </valueHelp> + <valueHelp> + <format>blowfish128</format> + <description>128 bit Blowfish-CBC</description> + </valueHelp> + <valueHelp> + <format>blowfish192</format> + <description>192 bit Blowfish-CBC</description> + </valueHelp> + <valueHelp> + <format>blowfish256</format> + <description>256 bit Blowfish-CBC</description> + </valueHelp> + <valueHelp> + <format>camellia128</format> + <description>128 bit Camellia-CBC</description> + </valueHelp> + <valueHelp> + <format>camellia192</format> + <description>192 bit Camellia-CBC</description> + </valueHelp> + <valueHelp> + <format>camellia256</format> + <description>256 bit Camellia-CBC</description> + </valueHelp> + <valueHelp> + <format>camellia128ctr</format> + <description>128 bit Camellia-COUNTER</description> + </valueHelp> + <valueHelp> + <format>camellia192ctr</format> + <description>192 bit Camellia-COUNTER</description> + </valueHelp> + <valueHelp> + <format>camellia256ctr</format> + <description>256 bit Camellia-COUNTER</description> + </valueHelp> + <valueHelp> + <format>camellia128ccm64</format> + <description>128 bit Camellia-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia192ccm64</format> + <description>192 bit Camellia-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia256ccm64</format> + <description>256 bit Camellia-CCM with 64 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia128ccm96</format> + <description>128 bit Camellia-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia192ccm96</format> + <description>192 bit Camellia-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia256ccm96</format> + <description>256 bit Camellia-CCM with 96 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia128ccm128</format> + <description>128 bit Camellia-CCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia192ccm128</format> + <description>192 bit Camellia-CCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>camellia256ccm128</format> + <description>256 bit Camellia-CCM with 128 bit ICV</description> + </valueHelp> + <valueHelp> + <format>serpent128</format> + <description>128 bit Serpent-CBC</description> + </valueHelp> + <valueHelp> + <format>serpent192</format> + <description>192 bit Serpent-CBC</description> + </valueHelp> + <valueHelp> + <format>serpent256</format> + <description>256 bit Serpent-CBC</description> + </valueHelp> + <valueHelp> + <format>twofish128</format> + <description>128 bit Twofish-CBC</description> + </valueHelp> + <valueHelp> + <format>twofish192</format> + <description>192 bit Twofish-CBC</description> + </valueHelp> + <valueHelp> + <format>twofish256</format> + <description>256 bit Twofish-CBC</description> + </valueHelp> + <valueHelp> + <format>cast128</format> + <description>128 bit CAST-CBC</description> + </valueHelp> + <valueHelp> + <format>chacha20poly1305</format> + <description>256 bit ChaCha20/Poly1305 with 128 bit ICV</description> + </valueHelp> + <constraint> + <regex>^(null|aes128|aes192|aes256|aes128ctr|aes192ctr|aes256ctr|aes128ccm64|aes192ccm64|aes256ccm64|aes128ccm96|aes192ccm96|aes256ccm96|aes128ccm128|aes192ccm128|aes256ccm128|aes128gcm64|aes192gcm64|aes256gcm64|aes128gcm96|aes192gcm96|aes256gcm96|aes128gcm128|aes192gcm128|aes256gcm128|aes128gmac|aes192gmac|aes256gmac|3des|blowfish128|blowfish192|blowfish256|camellia128|camellia192|camellia256|camellia128ctr|camellia192ctr|camellia256ctr|camellia128ccm64|camellia192ccm64|camellia256ccm64|camellia128ccm96|camellia192ccm96|camellia256ccm96|camellia128ccm128|camellia192ccm128|camellia256ccm128|serpent128|serpent192|serpent256|twofish128|twofish192|twofish256|cast128|chacha20poly1305)$</regex> + </constraint> + </properties> +</leafNode> <!-- include end --> diff --git a/interface-definitions/include/vpn-ipsec-hash.xml.i b/interface-definitions/include/vpn-ipsec-hash.xml.i index 93d57b622..5a06b290e 100644 --- a/interface-definitions/include/vpn-ipsec-hash.xml.i +++ b/interface-definitions/include/vpn-ipsec-hash.xml.i @@ -1,65 +1,65 @@ -<!-- include start from pn-ipsec-hash.xml.i --> - <leafNode name="hash"> - <properties> - <help>Hash algorithm</help> - <completionHelp> - <list>md5 md5_128 sha1 sha1_160 sha256 sha256_96 sha384 sha512 aesxcbc aescmac aes128gmac aes192gmac aes256gmac</list> - </completionHelp> - <valueHelp> - <format>md5</format> - <description>MD5 HMAC</description> - </valueHelp> - <valueHelp> - <format>md5_128</format> - <description>MD5_128 HMAC</description> - </valueHelp> - <valueHelp> - <format>sha1</format> - <description>SHA1 HMAC (default)</description> - </valueHelp> - <valueHelp> - <format>sha1_160</format> - <description>SHA1_160 HMAC</description> - </valueHelp> - <valueHelp> - <format>sha256</format> - <description>SHA2_256_128 HMAC</description> - </valueHelp> - <valueHelp> - <format>sha256_96</format> - <description>SHA2_256_96 HMAC</description> - </valueHelp> - <valueHelp> - <format>sha384</format> - <description>SHA2_384_192 HMAC</description> - </valueHelp> - <valueHelp> - <format>sha512</format> - <description>SHA2_512_256 HMAC</description> - </valueHelp> - <valueHelp> - <format>aesxcbc</format> - <description>AES XCBC</description> - </valueHelp> - <valueHelp> - <format>aescmac</format> - <description>AES CMAC</description> - </valueHelp> - <valueHelp> - <format>aes128gmac</format> - <description>128-bit AES-GMAC</description> - </valueHelp> - <valueHelp> - <format>aes192gmac</format> - <description>192-bit AES-GMAC</description> - </valueHelp> - <valueHelp> - <format>aes256gmac</format> - <description>256-bit AES-GMAC</description> - </valueHelp> - <constraint> - <regex>^(md5|md5_128|sha1|sha1_160|sha256|sha256_96|sha384|sha512|aesxcbc|aescmac|aes128gmac|aes192gmac|aes256gmac)$</regex> - </constraint> - </properties> - </leafNode> +<!-- include start from vpn-ipsec-hash.xml.i --> +<leafNode name="hash"> + <properties> + <help>Hash algorithm</help> + <completionHelp> + <list>md5 md5_128 sha1 sha1_160 sha256 sha256_96 sha384 sha512 aesxcbc aescmac aes128gmac aes192gmac aes256gmac</list> + </completionHelp> + <valueHelp> + <format>md5</format> + <description>MD5 HMAC</description> + </valueHelp> + <valueHelp> + <format>md5_128</format> + <description>MD5_128 HMAC</description> + </valueHelp> + <valueHelp> + <format>sha1</format> + <description>SHA1 HMAC (default)</description> + </valueHelp> + <valueHelp> + <format>sha1_160</format> + <description>SHA1_160 HMAC</description> + </valueHelp> + <valueHelp> + <format>sha256</format> + <description>SHA2_256_128 HMAC</description> + </valueHelp> + <valueHelp> + <format>sha256_96</format> + <description>SHA2_256_96 HMAC</description> + </valueHelp> + <valueHelp> + <format>sha384</format> + <description>SHA2_384_192 HMAC</description> + </valueHelp> + <valueHelp> + <format>sha512</format> + <description>SHA2_512_256 HMAC</description> + </valueHelp> + <valueHelp> + <format>aesxcbc</format> + <description>AES XCBC</description> + </valueHelp> + <valueHelp> + <format>aescmac</format> + <description>AES CMAC</description> + </valueHelp> + <valueHelp> + <format>aes128gmac</format> + <description>128-bit AES-GMAC</description> + </valueHelp> + <valueHelp> + <format>aes192gmac</format> + <description>192-bit AES-GMAC</description> + </valueHelp> + <valueHelp> + <format>aes256gmac</format> + <description>256-bit AES-GMAC</description> + </valueHelp> + <constraint> + <regex>^(md5|md5_128|sha1|sha1_160|sha256|sha256_96|sha384|sha512|aesxcbc|aescmac|aes128gmac|aes192gmac|aes256gmac)$</regex> + </constraint> + </properties> +</leafNode> <!-- include end --> diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index fff8db2d1..cb451f5be 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -9,14 +9,14 @@ <properties> <help>Ethernet Interface</help> <priority>318</priority> - <constraint> - <regex>^((eth|lan)[0-9]+|(eno|ens|enp|enx).+)$</regex> - </constraint> - <constraintErrorMessage>Invalid Ethernet interface name</constraintErrorMessage> <valueHelp> <format>ethN</format> <description>Ethernet interface name</description> </valueHelp> + <constraint> + <regex>^((eth|lan)[0-9]+|(eno|ens|enp|enx).+)$</regex> + </constraint> + <constraintErrorMessage>Invalid Ethernet interface name</constraintErrorMessage> </properties> <children> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> @@ -80,6 +80,12 @@ <valueless/> </properties> </leafNode> + <leafNode name="lro"> + <properties> + <help>Enable Large Receive Offload</help> + <valueless/> + </properties> + </leafNode> <leafNode name="rps"> <properties> <help>Enable Receive Packet Steering</help> diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 8c2b50eba..96479e057 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -25,23 +25,7 @@ <constraintErrorMessage>Access concentrator name must be composed of uppper and lower case letters or numbers only</constraintErrorMessage> </properties> </leafNode> - <node name="authentication"> - <properties> - <help>Authentication settings</help> - </properties> - <children> - <leafNode name="user"> - <properties> - <help>User name</help> - </properties> - </leafNode> - <leafNode name="password"> - <properties> - <help>Password</help> - </properties> - </leafNode> - </children> - </node> + #include <include/interface/authentication.xml.i> #include <include/interface/interface-dial-on-demand.xml.i> <leafNode name="default-route"> <properties> diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index 604d7dd29..10e1feb6b 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -32,6 +32,7 @@ #include <include/interface/interface-description.xml.i> #include <include/interface/interface-disable.xml.i> #include <include/interface/interface-mtu-68-16000.xml.i> + #include <include/interface/interface-vrf.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/interfaces-wirelessmodem.xml.in b/interface-definitions/interfaces-wirelessmodem.xml.in deleted file mode 100644 index 25ac2d6e0..000000000 --- a/interface-definitions/interfaces-wirelessmodem.xml.in +++ /dev/null @@ -1,83 +0,0 @@ -<?xml version="1.0"?> -<interfaceDefinition> - <node name="interfaces"> - <children> - <tagNode name="wirelessmodem" owner="${vyos_conf_scripts_dir}/interfaces-wirelessmodem.py"> - <properties> - <help>Wireless Modem (WWAN) Interface</help> - <priority>350</priority> - <constraint> - <regex>^wlm[0-9]+$</regex> - </constraint> - <constraintErrorMessage>Wireless Modem interface must be named wlmN</constraintErrorMessage> - <valueHelp> - <format>wlmN</format> - <description>Wireless modem interface name</description> - </valueHelp> - </properties> - <children> - <leafNode name="apn"> - <properties> - <help>Access Point Name (APN)</help> - </properties> - </leafNode> - <node name="backup"> - <properties> - <help>Insert backup default route</help> - </properties> - <children> - <leafNode name="distance"> - <properties> - <help>Distance backup default route</help> - <valueHelp> - <format>1-255</format> - <description>Distance of the backup route (default: 10)</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-255"/> - </constraint> - <constraintErrorMessage>Must be between (1-255)</constraintErrorMessage> - </properties> - <defaultValue>10</defaultValue> - </leafNode> - </children> - </node> - #include <include/interface/interface-description.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-vrf.xml.i> - <leafNode name="device"> - <properties> - <help>Serial device </help> - <completionHelp> - <script>ls -1 /dev | grep ttyS</script> - <script>if [ -d /dev/serial/by-bus ]; then ls -1 /dev/serial/by-bus; fi</script> - </completionHelp> - <valueHelp> - <format>ttySXX</format> - <description>TTY device name, regular serial port</description> - </valueHelp> - <valueHelp> - <format>usbNbXpY</format> - <description>TTY device name, USB based</description> - </valueHelp> - <constraint> - <regex>^(ttyS[0-9]+|usb[0-9]+b.*)$</regex> - </constraint> - </properties> - </leafNode> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-mtu-68-16000.xml.i> - #include <include/interface/interface-ipv4-options.xml.i> - #include <include/interface/interface-ipv6-options.xml.i> - <leafNode name="no-peer-dns"> - <properties> - <help>Do not use peer supplied DNS server information</help> - <valueless/> - </properties> - </leafNode> - #include <include/interface/interface-dial-on-demand.xml.i> - </children> - </tagNode> - </children> - </node> -</interfaceDefinition> diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in new file mode 100644 index 000000000..ea3184a11 --- /dev/null +++ b/interface-definitions/interfaces-wwan.xml.in @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="wwan" owner="${vyos_conf_scripts_dir}/interfaces-wwan.py"> + <properties> + <help>Wireless Modem (WWAN) Interface</help> + <priority>350</priority> + <completionHelp> + <script>cd /sys/class/net; ls -d wwan*</script> + </completionHelp> + <constraint> + <regex>^wwan[0-9]+$</regex> + </constraint> + <constraintErrorMessage>Wireless Modem interface must be named wwanN</constraintErrorMessage> + <valueHelp> + <format>wwanN</format> + <description>Wireless Wide Area Network interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> + <leafNode name="apn"> + <properties> + <help>Access Point Name (APN)</help> + </properties> + </leafNode> + #include <include/interface/dhcp-options.xml.i> + #include <include/interface/dhcpv6-options.xml.i> + #include <include/interface/authentication.xml.i> + #include <include/interface/interface-description.xml.i> + #include <include/interface/interface-disable.xml.i> + #include <include/interface/interface-vrf.xml.i> + #include <include/interface/interface-disable-link-detect.xml.i> + #include <include/interface/interface-mtu-68-1500.xml.i> + <leafNode name="mtu"> + <defaultValue>1430</defaultValue> + </leafNode> + #include <include/interface/interface-ipv4-options.xml.i> + #include <include/interface/interface-ipv6-options.xml.i> + #include <include/interface/interface-dial-on-demand.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index 195e074a3..6a002cc20 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -925,15 +925,6 @@ <valueless/> </properties> </leafNode> - <leafNode name="bgp-extcommunity-rt"> - <properties> - <help>Set route target value</help> - <valueHelp> - <format><aa:nn></format> - <description>ExtCommunity in format: asn:value</description> - </valueHelp> - </properties> - </leafNode> <node name="comm-list"> <properties> <help>Border Gateway Protocol (BGP) communities matching a community-list</help> @@ -1007,24 +998,71 @@ </constraint> </properties> </leafNode> - <leafNode name="extcommunity-rt"> + <node name="extcommunity"> <properties> - <help>Set route target value</help> - <valueHelp> - <format>txt</format> - <description>ASN:nn_or_IP_address:nn VPN extended community</description> - </valueHelp> + <help>BGP extended community attribute</help> </properties> - </leafNode> - <leafNode name="extcommunity-soo"> - <properties> - <help>Set Site of Origin value</help> - <valueHelp> - <format>txt</format> - <description>ASN:nn_or_IP_address:nn VPN extended community</description> - </valueHelp> - </properties> - </leafNode> + <children> + <leafNode name="bandwidth"> + <properties> + <help>Bandwidth value in Mbps</help> + <completionHelp> + <list>cumulative num-multipaths</list> + </completionHelp> + <valueHelp> + <format>u32:1-25600</format> + <description>Bandwidth value in Mbps</description> + </valueHelp> + <valueHelp> + <format>cumulative</format> + <description>Cumulative bandwidth of all multipaths (outbound-only)</description> + </valueHelp> + <valueHelp> + <format>num-multipaths</format> + <description>Internally computed bandwidth based on number of multipaths (outbound-only)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-25600"/> + <regex>^(cumulative|num-multipaths)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="rt"> + <properties> + <help>Set route target value</help> + <valueHelp> + <format>ASN:NN</format> + <description>based on autonomous system number</description> + </valueHelp> + <valueHelp> + <format>IP:NN</format> + <description>Based on a router-id IP address</description> + </valueHelp> + <constraint> + <regex>^((?:[0-9]{1,3}\.){3}[0-9]{1,3}|\d+):\d+$</regex> + </constraint> + <constraintErrorMessage>Should be in form: ASN:NN or IPADDR:NN where ASN is autonomous system number</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="soo"> + <properties> + <help>Set Site of Origin value</help> + <valueHelp> + <format>ASN:NN</format> + <description>based on autonomous system number</description> + </valueHelp> + <valueHelp> + <format>IP:NN</format> + <description>Based on a router-id IP address</description> + </valueHelp> + <constraint> + <regex>^((?:[0-9]{1,3}\.){3}[0-9]{1,3}|\d+):\d+$</regex> + </constraint> + <constraintErrorMessage>Should be in form: ASN:NN or IPADDR:NN where ASN is autonomous system number</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> <leafNode name="ip-next-hop"> <properties> <help>Nexthop IP address</help> diff --git a/interface-definitions/service_mdns-repeater.xml.in b/interface-definitions/service_mdns-repeater.xml.in index 33ef9a434..d02dac8a6 100644 --- a/interface-definitions/service_mdns-repeater.xml.in +++ b/interface-definitions/service_mdns-repeater.xml.in @@ -23,6 +23,12 @@ <multi/> </properties> </leafNode> + <leafNode name="vrrp-disable"> + <properties> + <help>Disables mDNS repeater on VRRP interfaces not in MASTER state</help> + <valueless/> + </properties> + </leafNode> </children> </node> </children> diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in index 6faef9bd5..54742f1d0 100644 --- a/interface-definitions/ssh.xml.in +++ b/interface-definitions/ssh.xml.in @@ -1,5 +1,4 @@ <?xml version="1.0"?> -<!--SSH configuration --> <interfaceDefinition> <node name="service"> <properties> @@ -14,9 +13,7 @@ <children> <node name="access-control"> <properties> - <help>SSH user/group access controls. Directives are processed - in the following order: deny-users, allow-users, deny-groups and - allow-groups.</help> + <help>SSH user/group access controls</help> </properties> <children> <node name="allow"> @@ -24,26 +21,8 @@ <help>Allow user/group SSH access</help> </properties> <children> - <leafNode name="group"> - <properties> - <help>Allow members of a group to login</help> - <constraint> - <regex>[a-z_][a-z0-9_-]{1,31}[$]?</regex> - </constraint> - <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage> - <multi/> - </properties> - </leafNode> - <leafNode name="user"> - <properties> - <help>Allow specific users to login</help> - <constraint> - <regex>[a-z_][a-z0-9_-]{1,31}[$]?</regex> - </constraint> - <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage> - <multi/> - </properties> - </leafNode> + #include <include/ssh-group.xml.i> + #include <include/ssh-user.xml.i> </children> </node> <node name="deny"> @@ -51,26 +30,8 @@ <help>Deny user/group SSH access</help> </properties> <children> - <leafNode name="group"> - <properties> - <help>Disallow members of a group to login</help> - <constraint> - <regex>[a-z_][a-z0-9_-]{1,31}[$]?</regex> - </constraint> - <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage> - <multi/> - </properties> - </leafNode> - <leafNode name="user"> - <properties> - <help>Disallow specific users to login</help> - <constraint> - <regex>[a-z_][a-z0-9_-]{1,31}[$]?</regex> - </constraint> - <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage> - <multi/> - </properties> - </leafNode> + #include <include/ssh-group.xml.i> + #include <include/ssh-user.xml.i> </children> </node> </children> diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index 604f49cb6..2031217ba 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -642,6 +642,7 @@ <help>VPN IPSec Profile</help> </properties> <children> + #include <include/generic-disable-node.xml.i> <node name="authentication"> <properties> <help>Authentication [REQUIRED]</help> @@ -731,6 +732,7 @@ </valueHelp> </properties> <children> + #include <include/generic-disable-node.xml.i> <node name="authentication"> <properties> <help>Peer authentication [REQUIRED]</help> @@ -967,44 +969,6 @@ </valueHelp> </properties> <children> - <leafNode name="allow-nat-networks"> - <properties> - <help>Option to allow NAT networks</help> - <completionHelp> - <list>enable disable</list> - </completionHelp> - <valueHelp> - <format>enable</format> - <description>Enable NAT networks</description> - </valueHelp> - <valueHelp> - <format>disable</format> - <description>Disable NAT networks (default)</description> - </valueHelp> - <constraint> - <regex>^(enable|disable)$</regex> - </constraint> - </properties> - </leafNode> - <leafNode name="allow-public-networks"> - <properties> - <help>Option to allow public networks</help> - <completionHelp> - <list>enable disable</list> - </completionHelp> - <valueHelp> - <format>enable</format> - <description>Enable public networks</description> - </valueHelp> - <valueHelp> - <format>disable</format> - <description>Disable public networks (default)</description> - </valueHelp> - <constraint> - <regex>^(enable|disable)$</regex> - </constraint> - </properties> - </leafNode> #include <include/generic-disable-node.xml.i> <leafNode name="esp-group"> <properties> @@ -1047,6 +1011,7 @@ <validator name="ipv4-prefix"/> <validator name="ipv6-prefix"/> </constraint> + <multi/> </properties> </leafNode> </children> @@ -1085,6 +1050,7 @@ <validator name="ipv4-prefix"/> <validator name="ipv6-prefix"/> </constraint> + <multi/> </properties> </leafNode> </children> diff --git a/op-mode-definitions/connect.xml.in b/op-mode-definitions/connect.xml.in index 1ec62949a..8f19eac70 100644 --- a/op-mode-definitions/connect.xml.in +++ b/op-mode-definitions/connect.xml.in @@ -19,7 +19,7 @@ <help>Bring up a connection-oriented network interface</help> <completionHelp> <path>interfaces pppoe</path> - <path>interfaces wirelessmodem</path> + <path>interfaces wwan</path> </completionHelp> </properties> <command>sudo ${vyos_op_scripts_dir}/connect_disconnect.py --connect "$3"</command> diff --git a/op-mode-definitions/disconnect.xml.in b/op-mode-definitions/disconnect.xml.in index bf2c37b89..4415c0ed2 100644 --- a/op-mode-definitions/disconnect.xml.in +++ b/op-mode-definitions/disconnect.xml.in @@ -10,7 +10,7 @@ <help>Take down a connection-oriented network interface</help> <completionHelp> <path>interfaces pppoe</path> - <path>interfaces wirelessmodem</path> + <path>interfaces wwan</path> </completionHelp> </properties> <command>sudo ${vyos_op_scripts_dir}/connect_disconnect.py --disconnect "$3"</command> diff --git a/op-mode-definitions/show-interfaces-wirelessmodem.xml.in b/op-mode-definitions/show-interfaces-wirelessmodem.xml.in deleted file mode 100644 index 18b1e55c7..000000000 --- a/op-mode-definitions/show-interfaces-wirelessmodem.xml.in +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0"?> -<interfaceDefinition> - <node name="show"> - <children> - <node name="interfaces"> - <children> - <tagNode name="wirelessmodem"> - <properties> - <help>Show Wireless Modem (WWAN) interface information</help> - <completionHelp> - <path>interfaces wirelessmodem</path> - </completionHelp> - </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> - <children> - <leafNode name="log"> - <properties> - <help>Show specified WWAN interface log</help> - </properties> - <command>/usr/bin/journalctl --unit "ppp@$4".service</command> - </leafNode> - <leafNode name="statistics"> - <properties> - <help>Show specified WWAN interface statistics</help> - <completionHelp> - <path>interfaces wirelessmodem</path> - </completionHelp> - </properties> - <command>if [ -d "/sys/class/net/$4" ]; then /usr/sbin/pppstats "$4"; fi</command> - </leafNode> - </children> - </tagNode> - <node name="wirelessmodem"> - <properties> - <help>Show Wireless Modem (WWAN) interface information</help> - </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wirelessmodem --action=show-brief</command> - <children> - <leafNode name="detail"> - <properties> - <help>Show detailed Wireless Modem (WWAN( interface information</help> - </properties> - <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wirelessmodem --action=show</command> - </leafNode> - </children> - </node> - </children> - </node> - </children> - </node> -</interfaceDefinition> diff --git a/op-mode-definitions/show-interfaces-wwan.xml.in b/op-mode-definitions/show-interfaces-wwan.xml.in new file mode 100644 index 000000000..d57e17a13 --- /dev/null +++ b/op-mode-definitions/show-interfaces-wwan.xml.in @@ -0,0 +1,103 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="wwan"> + <properties> + <help>Show Wireless Wire Area Network (WWAN) interface information</help> + <completionHelp> + <path>interfaces wwan</path> + <script>cd /sys/class/net; ls -d wwan*</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <children> + <leafNode name="capabilities"> + <properties> + <help>Show WWAN module capabilities</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --capabilities</command> + </leafNode> + <leafNode name="firmware"> + <properties> + <help>Show WWAN module firmware</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --firmware</command> + </leafNode> + <leafNode name="imei"> + <properties> + <help>Show WWAN module IMEI/ESN/MEID</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --imei</command> + </leafNode> + <leafNode name="imsi"> + <properties> + <help>Show WWAN module IMSI</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --imsi</command> + </leafNode> + <leafNode name="model"> + <properties> + <help>Show WWAN module manufacturer</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --model</command> + </leafNode> + <leafNode name="msisdn"> + <properties> + <help>Show WWAN module MSISDN</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --msisdn</command> + </leafNode> + <leafNode name="revision"> + <properties> + <help>Show WWAN module revision</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --revision</command> + </leafNode> + <leafNode name="signal"> + <properties> + <help>Show WWAN module RF signal info</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --signal</command> + </leafNode> + <leafNode name="sim"> + <properties> + <help>Show WWAN module connected SIM card information</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/show_wwan.py --interface=$4 --sim</command> + </leafNode> + <leafNode name="summary"> + <properties> + <help>Show WWAN module information summary</help> + </properties> + <command>mmcli --modem ${4#wwan}</command> + </leafNode> + <leafNode name="log"> + <properties> + <help>Show interface log for specified interface</help> + </properties> + <command>echo not implemented</command> + </leafNode> + </children> + </tagNode> + <node name="wwan"> + <properties> + <help>Show Wireless Modem (WWAN) interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wirelessmodem --action=show-brief</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed Wireless Modem (WWAN( interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wirelessmodem --action=show</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index bb2de1580..92c1cf016 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -123,6 +123,12 @@ </tagNode> </children> </tagNode> + <leafNode name="kernel"> + <properties> + <help>Show messages in kernel ring buffer</help> + </properties> + <command>sudo dmesg</command> + </leafNode> <leafNode name="lldp"> <properties> <help>Show log for LLDP</help> diff --git a/op-mode-definitions/show-version.xml.in b/op-mode-definitions/show-version.xml.in index 6bc49b8cf..8b7cc7e58 100644 --- a/op-mode-definitions/show-version.xml.in +++ b/op-mode-definitions/show-version.xml.in @@ -26,6 +26,12 @@ </properties> <command>vtysh -c "show version"</command> </leafNode> + <leafNode name="kernel"> + <properties> + <help>Show Linux Kernel version information</help> + </properties> + <command>uname -r</command> + </leafNode> </children> </node> </children> diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 88cbf2d5b..979e28b11 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -166,6 +166,19 @@ def verify_mirror(config): raise ConfigError(f'Can not mirror "{direction}" traffic back ' \ 'the originating interface!') +def verify_authentication(config): + """ + Common helper function used by interface implementations to perform + recurring validation of authentication for either PPPoE or WWAN interfaces. + + If authentication CLI option is defined, both username and password must + be set! + """ + if 'authentication' not in config: + return + if not {'user', 'password'} <= set(config['authentication']): + raise ConfigError('Authentication requires both username and ' \ + 'password to be set!') def verify_address(config): """ diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index e9da1e9f5..2d3e406ac 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -35,3 +35,4 @@ from vyos.ifconfig.tunnel import TunnelIf from vyos.ifconfig.wireless import WiFiIf from vyos.ifconfig.l2tpv3 import L2TPv3If from vyos.ifconfig.macsec import MACsecIf +from vyos.ifconfig.wwan import WWANIf diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index b89ca5a5c..07b31a12a 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -55,6 +55,11 @@ class EthernetIf(Interface): 'possible': lambda i, v: EthernetIf.feature(i, 'gso', v), # 'shellcmd': 'ethtool -K {ifname} gso {value}', }, + 'lro': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'lro', v), + # 'shellcmd': 'ethtool -K {ifname} lro {value}', + }, 'sg': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'possible': lambda i, v: EthernetIf.feature(i, 'sg', v), @@ -238,6 +243,18 @@ class EthernetIf(Interface): raise ValueError("Value out of range") return self.set_interface('gso', 'on' if state else 'off') + def set_lro(self, state): + """ + Enable Large Receive offload. State can be either True or False. + Example: + >>> from vyos.ifconfig import EthernetIf + >>> i = EthernetIf('eth0') + >>> i.set_lro(True) + """ + if not isinstance(state, bool): + raise ValueError("Value out of range") + return self.set_interface('lro', 'on' if state else 'off') + def set_rps(self, state): if not isinstance(state, bool): raise ValueError("Value out of range") @@ -328,6 +345,9 @@ class EthernetIf(Interface): # GSO (generic segmentation offload) self.set_gso(dict_search('offload.gso', config) != None) + # LRO (large receive offload) + self.set_lro(dict_search('offload.lro', config) != None) + # RPS - Receive Packet Steering self.set_rps(dict_search('offload.rps', config) != None) diff --git a/python/vyos/ifconfig/wwan.py b/python/vyos/ifconfig/wwan.py new file mode 100644 index 000000000..f18959a60 --- /dev/null +++ b/python/vyos/ifconfig/wwan.py @@ -0,0 +1,28 @@ +# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class WWANIf(Interface): + iftype = 'wwan' + definition = { + **Interface.definition, + **{ + 'section': 'wwan', + 'prefixes': ['wwan', ], + 'eternal': 'wwan[0-9]+$', + }, + } diff --git a/python/vyos/remote.py b/python/vyos/remote.py index 05f739dc8..80a4e7528 100644 --- a/python/vyos/remote.py +++ b/python/vyos/remote.py @@ -144,35 +144,30 @@ def transfer_sftp(mode, local_path, hostname, remote_path,\ sock.bind((source, 0)) sock.connect((hostname, port)) callback = make_progressbar() if progressbar else None - try: - with SSHClient() as ssh: - ssh.load_system_host_keys() - if os.path.exists(KNOWN_HOSTS_FILE): - ssh.load_host_keys(KNOWN_HOSTS_FILE) - ssh.set_missing_host_key_policy(InteractivePolicy()) - ssh.connect(hostname, port, username, password, sock=sock) - with ssh.open_sftp() as sftp: - if mode == 'upload': - try: - # If the remote path is a directory, use the original filename. - if stat.S_ISDIR(sftp.stat(remote_path).st_mode): - path = os.path.join(remote_path, os.path.basename(local_path)) - # A file exists at this destination. We're simply going to clobber it. - else: - path = remote_path - # This path doesn't point at any existing file. We can freely use this filename. - except IOError: + with SSHClient() as ssh: + ssh.load_system_host_keys() + if os.path.exists(KNOWN_HOSTS_FILE): + ssh.load_host_keys(KNOWN_HOSTS_FILE) + ssh.set_missing_host_key_policy(InteractivePolicy()) + ssh.connect(hostname, port, username, password, sock=sock) + with ssh.open_sftp() as sftp: + if mode == 'upload': + try: + # If the remote path is a directory, use the original filename. + if stat.S_ISDIR(sftp.stat(remote_path).st_mode): + path = os.path.join(remote_path, os.path.basename(local_path)) + # A file exists at this destination. We're simply going to clobber it. + else: path = remote_path - finally: - sftp.put(local_path, path, callback=callback) - elif mode == 'download': - sftp.get(remote_path, local_path, callback=callback) - elif mode == 'size': - return sftp.stat(remote_path).st_size - finally: - if sock: - sock.shutdown(socket.SHUT_RDWR) - sock.close() + # This path doesn't point at any existing file. We can freely use this filename. + except IOError: + path = remote_path + finally: + sftp.put(local_path, path, callback=callback) + elif mode == 'download': + sftp.get(remote_path, local_path, callback=callback) + elif mode == 'size': + return sftp.stat(remote_path).st_size def upload_sftp(*args, **kwargs): transfer_sftp('upload', *args, **kwargs) diff --git a/python/vyos/xml/test_xml.py b/python/vyos/xml/test_xml.py index ff55151d2..3a6f0132d 100644 --- a/python/vyos/xml/test_xml.py +++ b/python/vyos/xml/test_xml.py @@ -59,7 +59,7 @@ class TestSearch(TestCase): last = self.xml.traverse("interfaces") self.assertEqual(last, '') self.assertEqual(self.xml.inside, ['interfaces']) - self.assertEqual(self.xml.options, ['bonding', 'bridge', 'dummy', 'ethernet', 'geneve', 'l2tpv3', 'loopback', 'macsec', 'openvpn', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan', 'wireguard', 'wireless', 'wirelessmodem']) + self.assertEqual(self.xml.options, ['bonding', 'bridge', 'dummy', 'ethernet', 'geneve', 'l2tpv3', 'loopback', 'macsec', 'openvpn', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan', 'wireguard', 'wireless', 'wwan']) self.assertEqual(self.xml.filling, False) self.assertEqual(self.xml.word, '') self.assertEqual(self.xml.check, False) @@ -72,7 +72,7 @@ class TestSearch(TestCase): last = self.xml.traverse("interfaces ") self.assertEqual(last, '') self.assertEqual(self.xml.inside, ['interfaces']) - self.assertEqual(self.xml.options, ['bonding', 'bridge', 'dummy', 'ethernet', 'geneve', 'l2tpv3', 'loopback', 'macsec', 'openvpn', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan', 'wireguard', 'wireless', 'wirelessmodem']) + self.assertEqual(self.xml.options, ['bonding', 'bridge', 'dummy', 'ethernet', 'geneve', 'l2tpv3', 'loopback', 'macsec', 'openvpn', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan', 'wireguard', 'wireless', 'wwan']) self.assertEqual(self.xml.filling, False) self.assertEqual(self.xml.word, last) self.assertEqual(self.xml.check, False) @@ -85,7 +85,7 @@ class TestSearch(TestCase): last = self.xml.traverse("interfaces w") self.assertEqual(last, 'w') self.assertEqual(self.xml.inside, ['interfaces']) - self.assertEqual(self.xml.options, ['wireguard', 'wireless', 'wirelessmodem']) + self.assertEqual(self.xml.options, ['wireguard', 'wireless', 'wwan']) self.assertEqual(self.xml.filling, True) self.assertEqual(self.xml.word, last) self.assertEqual(self.xml.check, True) @@ -276,4 +276,4 @@ class TestSearch(TestCase): self.assertEqual(self.xml.filled, True) self.assertEqual(self.xml.plain, False) - # Need to add a check for a valuless leafNode
\ No newline at end of file + # Need to add a check for a valuless leafNode diff --git a/smoketest/configs/bgp-rpki b/smoketest/configs/bgp-rpki index e11ec9e72..dffab4c69 100644 --- a/smoketest/configs/bgp-rpki +++ b/smoketest/configs/bgp-rpki @@ -34,6 +34,13 @@ policy { local-preference 100 } } + rule 40 { + action permit + set { + extcommunity-rt 192.0.2.100:100 + extcommunity-soo 64500:100 + } + } } } protocols { diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py index b8682fe71..3412ebae0 100755 --- a/smoketest/scripts/cli/test_interfaces_pppoe.py +++ b/smoketest/scripts/cli/test_interfaces_pppoe.py @@ -179,5 +179,19 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): tmp = re.findall(f'systemctl restart dhcp6c@{interface}.service', tmp) self.assertTrue(tmp) + def test_pppoe_authentication(self): + # When username or password is set - so must be the other + interface = 'pppoe0' + self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos']) + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf']) + + # check validate() - if user is set, so must be the password + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos']) + self.cli_commit() + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_wirelessmodem.py b/smoketest/scripts/cli/test_interfaces_wirelessmodem.py deleted file mode 100755 index c36835ea7..000000000 --- a/smoketest/scripts/cli/test_interfaces_wirelessmodem.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020-2021 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os -import unittest - -from psutil import process_iter -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSession -from vyos.configsession import ConfigSessionError - -config_file = '/etc/ppp/peers/{}' -base_path = ['interfaces', 'wirelessmodem'] - -def get_config_value(interface, key): - with open(config_file.format(interface), 'r') as f: - for line in f: - if line.startswith(key): - return list(line.split()) - return [] - -class WWANInterfaceTest(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self._interfaces = ['wlm0', 'wlm1'] - - def tearDown(self): - self.cli_delete(base_path) - self.cli_commit() - - def test_wwan(self): - for interface in self._interfaces: - self.cli_set(base_path + [interface, 'no-peer-dns']) - self.cli_set(base_path + [interface, 'connect-on-demand']) - - # check validate() - APN must be configure - with self.assertRaises(ConfigSessionError): - self.cli_commit() - self.cli_set(base_path + [interface, 'apn', 'vyos.net']) - - # check validate() - device must be configure - with self.assertRaises(ConfigSessionError): - self.cli_commit() - self.cli_set(base_path + [interface, 'device', 'ttyS0']) - - # commit changes - self.cli_commit() - - # verify configuration file(s) - for interface in self._interfaces: - tmp = get_config_value(interface, 'ifname')[1] - self.assertTrue(interface in tmp) - - tmp = get_config_value(interface, 'demand')[0] - self.assertTrue('demand' in tmp) - - tmp = os.path.isfile(f'/etc/ppp/peers/chat.{interface}') - self.assertTrue(tmp) - - # Check if ppp process is running in the interface in question - running = False - for p in process_iter(): - if "pppd" in p.name(): - if interface in p.cmdline(): - running = True - - self.assertTrue(running) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 59425b789..2d7b78048 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -773,6 +773,9 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 'as-path-prepend' : '1234567890 987654321', 'atomic-aggregate' : '', 'distance' : '110', + 'extcommunity-bw' : '20000', + 'extcommunity-rt' : '123:456', + 'extcommunity-soo' : '456:789', 'ipv6-next-hop-global': '2001::1', 'ipv6-next-hop-local' : 'fe80::1', 'ip-next-hop' : '192.168.1.1', @@ -789,6 +792,18 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): }, }, }, + 'bandwidth-configuration' : { + 'rule' : { + '10' : { + 'action' : 'deny', + 'set' : { + 'as-path-prepend' : '100 100', + 'distance' : '200', + 'extcommunity-bw' : 'num-multipaths', + }, + }, + }, + }, } self.cli_set(['policy', 'access-list', access_list, 'rule', '10', 'action', 'permit']) @@ -896,6 +911,12 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_set(path + ['rule', rule, 'set', 'atomic-aggregate']) if 'distance' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'distance', rule_config['set']['distance']]) + if 'extcommunity-bw' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'bandwidth', rule_config['set']['extcommunity-bw']]) + if 'extcommunity-rt' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'rt', rule_config['set']['extcommunity-rt']]) + if 'extcommunity-soo' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'soo', rule_config['set']['extcommunity-soo']]) if 'ipv6-next-hop-global' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'ipv6-next-hop', 'global', rule_config['set']['ipv6-next-hop-global']]) if 'ipv6-next-hop-local' in rule_config['set']: @@ -1035,6 +1056,12 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): tmp += 'atomic-aggregate' elif 'distance' in rule_config['set']: tmp += 'distance ' + rule_config['set']['distance'] + elif 'extcommunity-bw' in rule_config['set']: + tmp += 'extcommunity bandwidth' + rule_config['set']['extcommunity-bw'] + elif 'extcommunity-rt' in rule_config['set']: + tmp += 'extcommunity rt' + rule_config['set']['extcommunity-rt'] + elif 'extcommunity-soo' in rule_config['set']: + tmp += 'extcommunity rt' + rule_config['set']['extcommunity-soo'] elif 'ip-next-hop' in rule_config['set']: tmp += 'ip next-hop ' + rule_config['set']['ip-next-hop'] elif 'ipv6-next-hop-global' in rule_config['set']: diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 10adc06e1..c51d83875 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -224,6 +224,10 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'graceful-shutdown']) self.cli_set(base_path + ['parameters', 'ebgp-requires-policy']) + self.cli_set(base_path + ['parameters', 'bestpath', 'as-path', 'multipath-relax']) + self.cli_set(base_path + ['parameters', 'bestpath', 'bandwidth', 'default-weight-for-missing']) + self.cli_set(base_path + ['parameters', 'bestpath', 'compare-routerid']) + # AFI maximum path support self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4]) self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ibgp', max_path_v4ibgp]) @@ -242,6 +246,9 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' no bgp default ipv4-unicast', frrconfig) self.assertIn(f' bgp graceful-restart stalepath-time {stalepath_time}', frrconfig) self.assertIn(f' bgp graceful-shutdown', frrconfig) + self.assertIn(f' bgp bestpath as-path multipath-relax', frrconfig) + self.assertIn(f' bgp bestpath bandwidth default-weight-for-missing', frrconfig) + self.assertIn(f' bgp bestpath compare-routerid', frrconfig) self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py index 4a3340ffb..b27ed3ca5 100755 --- a/smoketest/scripts/cli/test_vpn_ipsec.py +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -14,23 +14,63 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +import os import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.util import call, process_named_running, read_file +ethernet_path = ['interfaces', 'ethernet'] tunnel_path = ['interfaces', 'tunnel'] +vti_path = ['interfaces', 'vti'] nhrp_path = ['protocols', 'nhrp'] base_path = ['vpn', 'ipsec'] +dhcp_waiting_file = '/tmp/ipsec_dhcp_waiting' +swanctl_file = '/etc/swanctl/swanctl.conf' + class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): def tearDown(self): self.cli_delete(base_path) self.cli_delete(nhrp_path) self.cli_delete(tunnel_path) + self.cli_delete(vti_path) + self.cli_delete(ethernet_path) + self.cli_commit() + + def test_dhcp_fail_handling(self): + self.cli_delete(ethernet_path) + self.cli_delete(base_path) + + # Interface for dhcp-interface + self.cli_set(ethernet_path + ['eth0', 'vif', '100', 'address', 'dhcp']) # Use VLAN to avoid getting IP from qemu dhcp server + + # Set IKE/ESP Groups + self.cli_set(base_path + ["esp-group", "MyESPGroup", "proposal", "1", "encryption", "aes128"]) + self.cli_set(base_path + ["esp-group", "MyESPGroup", "proposal", "1", "hash", "sha1"]) + self.cli_set(base_path + ["ike-group", "MyIKEGroup", "proposal", "1", "dh-group", "2"]) + self.cli_set(base_path + ["ike-group", "MyIKEGroup", "proposal", "1", "encryption", "aes128"]) + self.cli_set(base_path + ["ike-group", "MyIKEGroup", "proposal", "1", "hash", "sha1"]) + + # Site to site + self.cli_set(base_path + ["ipsec-interfaces", "interface", "eth0.100"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "authentication", "mode", "pre-shared-secret"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "authentication", "pre-shared-secret", "MYSECRETKEY"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "ike-group", "MyIKEGroup"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "default-esp-group", "MyESPGroup"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "dhcp-interface", "eth0.100"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "protocol", "gre"]) + self.cli_commit() + self.assertTrue(os.path.exists(dhcp_waiting_file)) + + dhcp_waiting = read_file(dhcp_waiting_file) + self.assertIn('eth0.100', dhcp_waiting) # Ensure dhcp-failed interface was added for dhclient hook + + self.assertTrue(process_named_running('charon')) # Commit should've still succeeded and launched charon + def test_site_to_site(self): self.cli_delete(base_path) @@ -40,6 +80,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ["ike-group", "MyIKEGroup", "proposal", "1", "dh-group", "2"]) self.cli_set(base_path + ["ike-group", "MyIKEGroup", "proposal", "1", "encryption", "aes128"]) self.cli_set(base_path + ["ike-group", "MyIKEGroup", "proposal", "1", "hash", "sha1"]) + self.cli_set(base_path + ["ike-group", "MyIKEGroup", "key-exchange", "ikev2"]) # Site to site self.cli_set(base_path + ["ipsec-interfaces", "interface", "eth0"]) @@ -48,33 +89,104 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "ike-group", "MyIKEGroup"]) self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "default-esp-group", "MyESPGroup"]) self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "local-address", "192.0.2.10"]) - self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "protocol", "gre"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "protocol", "tcp"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "local", "prefix", "172.16.10.0/24"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "local", "prefix", "172.16.11.0/24"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "local", "port", "443"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "remote", "prefix", "172.17.10.0/24"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "remote", "prefix", "172.17.11.0/24"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "remote", "port", "443"]) self.cli_commit() - ipsec_conf_lines = [ - 'authby = secret', - 'ike = aes128-sha1-modp1024!', - 'esp = aes128-sha1-modp1024!', - 'left = 192.0.2.10', - 'right = 203.0.113.45', - 'type = tunnel' + swanctl_conf_lines = [ + 'version = 2', + 'auth = psk', + 'proposals = aes128-sha1-modp1024', + 'esp_proposals = aes128-sha1-modp1024', + 'local_addrs = 192.0.2.10 # dhcp:no', + 'remote_addrs = 203.0.113.45', + 'mode = tunnel', + 'local_ts = 172.16.10.0/24[tcp/443],172.16.11.0/24[tcp/443]', + 'remote_ts = 172.17.10.0/24[tcp/443],172.17.11.0/24[tcp/443]' + ] + + swanctl_secrets_lines = [ + 'id-local = 192.0.2.10 # dhcp:no', + 'id-remote = 203.0.113.45', + 'secret = "MYSECRETKEY"' ] - ipsec_secrets_lines = [ - '192.0.2.10 203.0.113.45 : PSK "MYSECRETKEY" # dhcp:no' + tmp_swanctl_conf = read_file(swanctl_file) + + for line in swanctl_conf_lines: + self.assertIn(line, tmp_swanctl_conf) + + for line in swanctl_secrets_lines: + self.assertIn(line, tmp_swanctl_conf) + + # Check for running process + self.assertTrue(process_named_running('charon')) + + def test_site_to_site_vti(self): + self.cli_delete(base_path) + self.cli_delete(vti_path) + + # VTI interface + self.cli_set(vti_path + ["vti10", "address", "10.1.1.1/24"]) + + # IKE/ESP Groups + self.cli_set(base_path + ["esp-group", "MyESPGroup", "proposal", "1", "encryption", "aes128"]) + self.cli_set(base_path + ["esp-group", "MyESPGroup", "proposal", "1", "hash", "sha1"]) + self.cli_set(base_path + ["ike-group", "MyIKEGroup", "proposal", "1", "dh-group", "2"]) + self.cli_set(base_path + ["ike-group", "MyIKEGroup", "proposal", "1", "encryption", "aes128"]) + self.cli_set(base_path + ["ike-group", "MyIKEGroup", "proposal", "1", "hash", "sha1"]) + self.cli_set(base_path + ["ike-group", "MyIKEGroup", "key-exchange", "ikev2"]) + + # Site to site + self.cli_set(base_path + ["ipsec-interfaces", "interface", "eth0"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "authentication", "mode", "pre-shared-secret"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "authentication", "pre-shared-secret", "MYSECRETKEY"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "ike-group", "MyIKEGroup"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "default-esp-group", "MyESPGroup"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "local-address", "192.0.2.10"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "local", "prefix", "172.16.10.0/24"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "local", "prefix", "172.16.11.0/24"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "remote", "prefix", "172.17.10.0/24"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "tunnel", "1", "remote", "prefix", "172.17.11.0/24"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "vti", "bind", "vti10"]) + self.cli_set(base_path + ["site-to-site", "peer", "203.0.113.45", "vti", "esp-group", "MyESPGroup"]) + + self.cli_commit() + + swanctl_conf_lines = [ + 'version = 2', + 'auth = psk', + 'proposals = aes128-sha1-modp1024', + 'esp_proposals = aes128-sha1-modp1024', + 'local_addrs = 192.0.2.10 # dhcp:no', + 'remote_addrs = 203.0.113.45', + 'mode = tunnel', + 'local_ts = 172.16.10.0/24,172.16.11.0/24', + 'remote_ts = 172.17.10.0/24,172.17.11.0/24', + 'mark_in = 9437194', # 0x900000 + (vti)10 + 'mark_out = 9437194', + 'updown = "/etc/ipsec.d/vti-up-down vti10 no"' ] - tmp_ipsec_conf = read_file('/etc/ipsec.conf') + swanctl_secrets_lines = [ + 'id-local = 192.0.2.10 # dhcp:no', + 'id-remote = 203.0.113.45', + 'secret = "MYSECRETKEY"' + ] - for line in ipsec_conf_lines: - self.assertIn(line, tmp_ipsec_conf) + tmp_swanctl_conf = read_file(swanctl_file) - call('sudo chmod 644 /etc/ipsec.secrets') # Needs to be readable - tmp_ipsec_secrets = read_file('/etc/ipsec.secrets') + for line in swanctl_conf_lines: + self.assertIn(line, tmp_swanctl_conf) - for line in ipsec_secrets_lines: - self.assertIn(line, tmp_ipsec_secrets) + for line in swanctl_secrets_lines: + self.assertIn(line, tmp_swanctl_conf) # Check for running process self.assertTrue(process_named_running('charon')) diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index 591630c46..f36d16344 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -119,6 +119,24 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): self.assertTrue(is_intf_addr_assigned(vrf, '127.0.0.1')) self.assertTrue(is_intf_addr_assigned(vrf, '::1')) + def test_vrf_bind_all(self): + table = '2000' + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', str(table)]) + table = str(int(table) + 1) + + self.cli_set(base_path + ['bind-to-all']) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + tmp = read_file('/proc/sys/net/ipv4/tcp_l3mdev_accept') + self.assertIn(tmp, '1') + tmp = read_file('/proc/sys/net/ipv4/udp_l3mdev_accept') + self.assertIn(tmp, '1') + def test_vrf_table_id_is_unalterable(self): # Linux Kernel prohibits the change of a VRF table on the fly. # VRF must be deleted and recreated! diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 5efdb6a2f..21b47f42a 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.py @@ -75,7 +75,7 @@ def get_config(config=None): base = ['container'] container = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True) + get_first_key=True, no_tag_node_value_mangle=True) # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. default_values = defaults(base) diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 3675db73b..6c4c6c95b 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -22,6 +22,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configverify import verify_authentication from vyos.configverify import verify_source_interface from vyos.configverify import verify_vrf from vyos.configverify import verify_mtu_ipv6 @@ -51,6 +52,7 @@ def verify(pppoe): return None verify_source_interface(pppoe) + verify_authentication(pppoe) verify_vrf(pppoe) verify_mtu_ipv6(pppoe) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 1575c83ef..294da8ef9 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -114,7 +114,7 @@ def verify(tunnel): raise ConfigError('Option ignore-df can only be used on GRETAP tunnels!') if dict_search('parameters.ip.no_pmtu_discovery', tunnel) == None: - raise ConfigError('Option ignore-df path MTU discovery to be disabled!') + raise ConfigError('Option ignore-df requires path MTU discovery to be disabled!') def generate(tunnel): diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py deleted file mode 100755 index 976953b31..000000000 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os - -from sys import exit - -from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.configverify import verify_vrf -from vyos.template import render -from vyos.util import call -from vyos.util import check_kmod -from vyos.util import find_device_file -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -k_mod = ['option', 'usb_wwan', 'usbserial'] - -def get_config(config=None): - """ - Retrive CLI config as dictionary. Dictionary can never be empty, as at least the - interface name will be added or a deleted flag - """ - if config: - conf = config - else: - conf = Config() - base = ['interfaces', 'wirelessmodem'] - wwan = get_interface_dict(conf, base) - - return wwan - -def verify(wwan): - if 'deleted' in wwan: - return None - - if not 'apn' in wwan: - raise ConfigError('No APN configured for "{ifname}"'.format(**wwan)) - - if not 'device' in wwan: - raise ConfigError('Physical "device" must be configured') - - # we can not use isfile() here as Linux device files are no regular files - # thus the check will return False - 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) - - return None - -def generate(wwan): - # set up configuration file path variables where our templates will be - # rendered into - ifname = wwan['ifname'] - config_wwan = f'/etc/ppp/peers/{ifname}' - config_wwan_chat = f'/etc/ppp/peers/chat.{ifname}' - script_wwan_pre_up = f'/etc/ppp/ip-pre-up.d/1010-vyos-wwan-{ifname}' - script_wwan_ip_up = f'/etc/ppp/ip-up.d/1010-vyos-wwan-{ifname}' - script_wwan_ip_down = f'/etc/ppp/ip-down.d/1010-vyos-wwan-{ifname}' - - config_files = [config_wwan, config_wwan_chat, script_wwan_pre_up, - script_wwan_ip_up, script_wwan_ip_down] - - # Always hang-up WWAN connection prior generating new configuration file - call(f'systemctl stop ppp@{ifname}.service') - - if 'deleted' in wwan: - # Delete PPP configuration files - for file in config_files: - if os.path.exists(file): - os.unlink(file) - - else: - wwan['device'] = find_device_file(wwan['device']) - - # Create PPP configuration files - render(config_wwan, 'wwan/peer.tmpl', wwan) - # Create PPP chat script - render(config_wwan_chat, 'wwan/chat.tmpl', wwan) - - # generated script file must be executable - - # Create script for ip-pre-up.d - render(script_wwan_pre_up, 'wwan/ip-pre-up.script.tmpl', - wwan, permission=0o755) - # Create script for ip-up.d - render(script_wwan_ip_up, 'wwan/ip-up.script.tmpl', - wwan, permission=0o755) - # Create script for ip-down.d - render(script_wwan_ip_down, 'wwan/ip-down.script.tmpl', - wwan, permission=0o755) - - return None - -def apply(wwan): - if 'deleted' in wwan: - # bail out early - return None - - if not 'disable' in wwan: - # "dial" WWAN connection - call('systemctl start ppp@{ifname}.service'.format(**wwan)) - - return None - -if __name__ == '__main__': - try: - check_kmod(k_mod) - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py new file mode 100755 index 000000000..31c599145 --- /dev/null +++ b/src/conf_mode/interfaces-wwan.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_authentication +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_vrf +from vyos.ifconfig import WWANIf +from vyos.util import cmd +from vyos.util import dict_search +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'wwan'] + wwan = get_interface_dict(conf, base) + + return wwan + +def verify(wwan): + if 'deleted' in wwan: + return None + + ifname = wwan['ifname'] + if not 'apn' in wwan: + raise ConfigError(f'No APN configured for "{ifname}"!') + + verify_interface_exists(ifname) + verify_authentication(wwan) + verify_vrf(wwan) + + return None + +def generate(wwan): + return None + +def apply(wwan): + # we only need the modem number. wwan0 -> 0, wwan1 -> 1 + modem = wwan['ifname'].lstrip('wwan') + base_cmd = f'mmcli --modem {modem}' + # Number of bearers is limited - always disconnect first + cmd(f'{base_cmd} --simple-disconnect') + + w = WWANIf(wwan['ifname']) + if 'deleted' in wwan or 'disable' in wwan: + w.remove() + return None + + ip_type = 'ipv4' + slaac = dict_search('ipv6.address.autoconf', wwan) != None + if 'address' in wwan: + if 'dhcp' in wwan['address'] and ('dhcpv6' in wwan['address'] or slaac): + ip_type = 'ipv4v6' + elif 'dhcpv6' in wwan['address'] or slaac: + ip_type = 'ipv6' + elif 'dhcp' in wwan['address']: + ip_type = 'ipv4' + + options = f'ip-type={ip_type},apn=' + wwan['apn'] + if 'authentication' in wwan: + options += ',user={user},password={password}'.format(**wwan['authentication']) + + command = f'{base_cmd} --simple-connect="{options}"' + cmd(command) + w.update(wwan) + + 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/service_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py index 729518c96..c920920ed 100755 --- a/src/conf_mode/service_mdns-repeater.py +++ b/src/conf_mode/service_mdns-repeater.py @@ -16,10 +16,12 @@ import os +from json import loads from sys import exit from netifaces import ifaddresses, interfaces, AF_INET from vyos.config import Config +from vyos.ifconfig.vrrp import VRRP from vyos.template import render from vyos.util import call from vyos import ConfigError @@ -27,6 +29,7 @@ from vyos import airbag airbag.enable() config_file = r'/etc/default/mdns-repeater' +vrrp_running_file = '/run/mdns_vrrp_active' def get_config(config=None): if config: @@ -35,6 +38,9 @@ def get_config(config=None): conf = Config() base = ['service', 'mdns', 'repeater'] mdns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + if mdns: + mdns['vrrp_exists'] = conf.exists('high-availability vrrp') return mdns def verify(mdns): @@ -60,6 +66,18 @@ def verify(mdns): return None +# Get VRRP states from interfaces, returns only interfaces where state is MASTER +def get_vrrp_master(interfaces): + json_data = loads(VRRP.collect('json')) + for group in json_data: + if 'data' in group: + if 'ifp_ifname' in group['data']: + iface = group['data']['ifp_ifname'] + state = group['data']['state'] # 2 = Master + if iface in interfaces and state != 2: + interfaces.remove(iface) + return interfaces + def generate(mdns): if not mdns: return None @@ -68,6 +86,12 @@ def generate(mdns): print('Warning: mDNS repeater will be deactivated because it is disabled') return None + if mdns['vrrp_exists'] and 'vrrp_disable' in mdns: + mdns['interface'] = get_vrrp_master(mdns['interface']) + + if len(mdns['interface']) < 2: + return None + render(config_file, 'mdns-repeater/mdns-repeater.tmpl', mdns) return None @@ -76,7 +100,21 @@ def apply(mdns): call('systemctl stop mdns-repeater.service') if os.path.exists(config_file): os.unlink(config_file) + + if os.path.exists(vrrp_running_file): + os.unlink(vrrp_running_file) else: + if 'vrrp_disable' not in mdns and os.path.exists(vrrp_running_file): + os.unlink(vrrp_running_file) + + if mdns['vrrp_exists'] and 'vrrp_disable' in mdns: + if not os.path.exists(vrrp_running_file): + os.mknod(vrrp_running_file) # vrrp script looks for this file to update mdns repeater + + if len(mdns['interface']) < 2: + call('systemctl stop mdns-repeater.service') + return None + call('systemctl restart mdns-repeater.service') return None diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index 4efedd995..433c51e7e 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -23,10 +23,11 @@ from vyos.config import Config from vyos.configdict import leaf_node_changed from vyos.configverify import verify_interface_exists from vyos.ifconfig import Interface +from vyos.template import ip_from_cidr from vyos.template import render +from vyos.validate import is_ipv6_link_local from vyos.util import call from vyos.util import dict_search -from vyos.util import get_interface_address from vyos.util import process_named_running from vyos.util import run from vyos.util import cidr_fit @@ -35,9 +36,9 @@ from vyos import airbag airbag.enable() authby_translate = { - 'pre-shared-secret': 'secret', - 'rsa': 'rsasig', - 'x509': 'rsasig' + 'pre-shared-secret': 'psk', + 'rsa': 'pubkey', + 'x509': 'pubkey' } default_pfs = 'dh-group2' pfs_translate = { @@ -73,12 +74,18 @@ any_log_modes = [ ike_ciphers = {} esp_ciphers = {} +dhcp_wait_attempts = 2 +dhcp_wait_sleep = 1 + mark_base = 0x900000 -CA_PATH = "/etc/ipsec.d/cacerts/" -CRL_PATH = "/etc/ipsec.d/crls/" +CERT_PATH="/etc/swanctl/x509/" +KEY_PATH="/etc/swanctl/private/" +CA_PATH = "/etc/swanctl/x509ca/" +CRL_PATH = "/etc/swanctl/x509crl/" DHCP_BASE = "/var/lib/dhcp/dhclient" +DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_waiting" LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/'] X509_PATH = '/config/auth/' @@ -96,6 +103,7 @@ def get_config(config=None): ipsec = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + ipsec['dhcp_no_address'] = {} ipsec['interface_change'] = leaf_node_changed(conf, base + ['ipsec-interfaces', 'interface']) ipsec['l2tp_exists'] = conf.exists('vpn l2tp remote-access ipsec-settings ') ipsec['nhrp_exists'] = conf.exists('protocols nhrp tunnel') @@ -119,7 +127,7 @@ def get_config(config=None): if enc and hash: ciphers.append(f"{enc}-{hash}-{pfs_translate[pfs]}" if pfs else f"{enc}-{hash}") - ike_ciphers[group] = ','.join(ciphers) + '!' + ike_ciphers[group] = ','.join(ciphers) if 'esp_group' in ipsec: for group, esp_conf in ipsec['esp_group'].items(): @@ -139,7 +147,7 @@ def get_config(config=None): hash = proposal['hash'] if 'hash' in proposal else None if enc and hash: ciphers.append(f"{enc}-{hash}-{pfs_translate[pfs]}" if pfs else f"{enc}-{hash}") - esp_ciphers[group] = ','.join(ciphers) + '!' + esp_ciphers[group] = ','.join(ciphers) return ipsec @@ -162,6 +170,15 @@ def verify_rsa_local_key(ipsec): def verify_rsa_key(ipsec, key_name): return dict_search(f'rsa_key_name.{key_name}.rsa_key', ipsec['rsa_keys']) +def get_dhcp_address(iface): + addresses = Interface(iface).get_addr() + if not addresses: + return None + for address in addresses: + if not is_ipv6_link_local(address): + return ip_from_cidr(address) + return None + def verify(ipsec): if not ipsec: return None @@ -252,9 +269,17 @@ def verify(ipsec): if not os.path.exists(f'{DHCP_BASE}_{dhcp_interface}.conf'): raise ConfigError(f"Invalid dhcp-interface on site-to-site peer {peer}") - address = Interface(dhcp_interface).get_addr() + address = get_dhcp_address(dhcp_interface) + count = 0 + while not address and count < dhcp_wait_attempts: + address = get_dhcp_address(dhcp_interface) + count += 1 + sleep(dhcp_wait_sleep) + if not address: - raise ConfigError(f"Failed to get address from dhcp-interface on site-to-site peer {peer}") + ipsec['dhcp_no_address'][peer] = dhcp_interface + print(f"Failed to get address from dhcp-interface on site-to-site peer {peer} -- skipped") + continue if 'vti' in peer_conf: if 'local_address' in peer_conf and 'dhcp_interface' in peer_conf: @@ -291,16 +316,28 @@ def generate(ipsec): data = {} if ipsec: + if ipsec['dhcp_no_address']: + with open(DHCP_HOOK_IFLIST, 'w') as f: + f.write(" ".join(ipsec['dhcp_no_address'].values())) + data = ipsec data['authby'] = authby_translate data['ciphers'] = {'ike': ike_ciphers, 'esp': esp_ciphers} data['marks'] = {} data['rsa_local_key'] = verify_rsa_local_key(ipsec) - data['x509_path'] = X509_PATH if 'site_to_site' in data and 'peer' in data['site_to_site']: for peer, peer_conf in ipsec['site_to_site']['peer'].items(): + if peer in ipsec['dhcp_no_address']: + continue + if peer_conf['authentication']['mode'] == 'x509': + cert_file = os.path.join(X509_PATH, peer_conf['authentication']['x509']['cert_file']) + call(f'cp -f {cert_file} {CERT_PATH}') + + key_file = os.path.join(X509_PATH, peer_conf['authentication']['x509']['key']['file']) + call(f'cp -f {key_file} {KEY_PATH}') + ca_cert_file = os.path.join(X509_PATH, peer_conf['authentication']['x509']['ca_cert_file']) call(f'cp -f {ca_cert_file} {CA_PATH}') @@ -312,22 +349,29 @@ def generate(ipsec): if 'local_address' in peer_conf: local_ip = peer_conf['local_address'] elif 'dhcp_interface' in peer_conf: - local_ip = Interface(peer_conf['dhcp_interface']).get_addr() + local_ip = get_dhcp_address(peer_conf['dhcp_interface']) data['site_to_site']['peer'][peer]['local_address'] = local_ip if 'vti' in peer_conf and 'bind' in peer_conf['vti']: vti_interface = peer_conf['vti']['bind'] data['marks'][vti_interface] = get_mark(vti_interface) - else: + + if 'tunnel' in peer_conf: for tunnel, tunnel_conf in peer_conf['tunnel'].items(): - local_prefix = dict_search('local.prefix', tunnel_conf) - remote_prefix = dict_search('remote.prefix', tunnel_conf) + local_prefixes = dict_search('local.prefix', tunnel_conf) + remote_prefixes = dict_search('remote.prefix', tunnel_conf) - if not local_prefix or not remote_prefix: + if not local_prefixes or not remote_prefixes: continue - passthrough = cidr_fit(local_prefix, remote_prefix) + passthrough = [] + + for local_prefix in local_prefixes: + for remote_prefix in remote_prefixes: + if cidr_fit(local_prefix, remote_prefix): + passthrough.append(local_prefix) + data['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough if 'logging' in ipsec and 'log_modes' in ipsec['logging']: diff --git a/src/conf_mode/vpn_rsa-keys.py b/src/conf_mode/vpn_rsa-keys.py index 6cf7eba6e..c6ff369ad 100755 --- a/src/conf_mode/vpn_rsa-keys.py +++ b/src/conf_mode/vpn_rsa-keys.py @@ -29,7 +29,8 @@ from Crypto.PublicKey.RSA import construct airbag.enable() LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/'] -LOCAL_OUTPUT = '/etc/ipsec.d/certs/localhost.pub' +LOCAL_OUTPUT = '/etc/swanctl/pubkey/localhost.pub' +LOCAL_KEY_OUTPUT = '/etc/swanctl/private/localhost.key' def get_config(config=None): if config: @@ -68,6 +69,7 @@ def generate(conf): if 'local_key' in conf and 'file' in conf['local_key']: local_key = conf['local_key']['file'] local_key_path = get_local_key(local_key) + call(f'sudo cp -f {local_key_path} {LOCAL_KEY_OUTPUT}') call(f'sudo /usr/bin/openssl rsa -in {local_key_path} -pubout -out {LOCAL_OUTPUT}') if 'rsa_key_name' in conf: @@ -82,7 +84,7 @@ def generate(conf): else: remote_key = bytes('-----BEGIN PUBLIC KEY-----\n' + remote_key + '\n-----END PUBLIC KEY-----\n', 'utf-8') - with open(f'/etc/ipsec.d/certs/{key_name}.pub', 'wb') as f: + with open(f'/etc/swanctl/pubkey/{key_name}.pub', 'wb') as f: f.write(remote_key) def migrate_from_vyatta_key(data): diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index a39da8991..936561edc 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -141,7 +141,7 @@ def apply(vrf): # set the default VRF global behaviour bind_all = '0' - if 'bind_to_all' in vrf: + if 'bind-to-all' in vrf: bind_all = '1' call(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}') call(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}') diff --git a/src/etc/cron.hourly/vyos-logrotate-hourly b/src/etc/cron.hourly/vyos-logrotate-hourly new file mode 100755 index 000000000..f4f56a9c2 --- /dev/null +++ b/src/etc/cron.hourly/vyos-logrotate-hourly @@ -0,0 +1,4 @@ +#!/bin/sh + +test -x /usr/sbin/logrotate || exit 0 +/usr/sbin/logrotate /etc/logrotate.conf diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook index 36edf04f3..a7a9a2ce6 100644..100755 --- a/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook +++ b/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook @@ -1,12 +1,44 @@ -#!/usr/bin/env python3 +#!/bin/bash +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. -import os -import sys +if [ "$reason" == "REBOOT" ] || [ "$reason" == "EXPIRE" ]; then + exit 0 +fi + +DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_waiting" + +if [ -f $DHCP_HOOK_IFLIST ] && [ "$reason" == "BOUND" ]; then + if grep -qw $interface $DHCP_HOOK_IFLIST; then + sudo rm $DHCP_HOOK_IFLIST + sudo python3 /usr/libexec/vyos/conf_mode/vpn_ipsec.py + exit 0 + fi +fi + +if [ "$old_ip_address" == "$new_ip_address" ] && [ "$reason" == "BOUND" ]; then + exit 0 +fi +python3 - <<PYEND +import os +import re from vyos.util import call +from vyos.util import cmd -IPSEC_CONF="/etc/ipsec.conf" -IPSEC_SECRETS="/etc/ipsec.secrets" +SWANCTL_CONF="/etc/swanctl/swanctl.conf" def getlines(file): with open(file, 'r') as f: @@ -16,17 +48,25 @@ def writelines(file, lines): with open(file, 'w') as f: f.writelines(lines) +def ipsec_down(ip_address): + # This prevents the need to restart ipsec and kill all active connections, only the stale connection is closed + status = cmd('sudo ipsec statusall') + connection_name = None + for line in status.split("\n"): + if line.find(ip_address) > 0: + regex_match = re.search(r'(peer_[^:\[]+)', line) + if regex_match: + connection_name = regex_match[1] + break + if connection_name: + call(f'sudo ipsec down {connection_name}') + if __name__ == '__main__': interface = os.getenv('interface') new_ip = os.getenv('new_ip_address') old_ip = os.getenv('old_ip_address') - reason = os.getenv('reason') - - if (old_ip == new_ip and reason != 'BOUND') or reason in ['REBOOT', 'EXPIRE']: - sys.exit(0) - conf_lines = getlines(IPSEC_CONF) - secrets_lines = getlines(IPSEC_SECRETS) + conf_lines = getlines(SWANCTL_CONF) found = False to_match = f'# dhcp:{interface}' @@ -40,7 +80,9 @@ if __name__ == '__main__': secrets_lines[i] = line.replace(old_ip, new_ip) if found: - writelines(IPSEC_CONF, conf_lines) - writelines(IPSEC_SECRETS, secrets_lines) - call('sudo /usr/sbin/ipsec rereadall') - call('sudo /usr/sbin/ipsec reload') + writelines(SWANCTL_CONF, conf_lines) + ipsec_down(old_ip) + call('sudo ipsec rereadall') + call('sudo ipsec reload') + call('sudo swanctl -q') +PYEND
\ No newline at end of file diff --git a/src/etc/ipsec.d/vti-up-down b/src/etc/ipsec.d/vti-up-down index 0e1cd7753..2b66dd9e6 100755 --- a/src/etc/ipsec.d/vti-up-down +++ b/src/etc/ipsec.d/vti-up-down @@ -1,4 +1,18 @@ #!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. ## Script called up strongswan to bring the vti interface up/down based on the state of the IPSec tunnel. ## Called as vti_up_down vti_intf_name diff --git a/src/etc/systemd/system/ModemManager.service.d/override.conf b/src/etc/systemd/system/ModemManager.service.d/override.conf new file mode 100644 index 000000000..07a18460e --- /dev/null +++ b/src/etc/systemd/system/ModemManager.service.d/override.conf @@ -0,0 +1,7 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +ExecStart= +ExecStart=/usr/sbin/ModemManager --filter-policy=strict --log-level=INFO --log-timestamps --log-journal 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/18-to-19 b/src/migration-scripts/interfaces/18-to-19 index 06e07572f..a12c4a6cd 100755 --- a/src/migration-scripts/interfaces/18-to-19 +++ b/src/migration-scripts/interfaces/18-to-19 @@ -14,65 +14,31 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +import os + from sys import argv from sys import exit -from vyos.configtree import ConfigTree - -def migrate_ospf(config, path, interface): - path = path + ['ospf'] - if config.exists(path): - new_base = ['protocols', 'ospf', 'interface'] - config.set(new_base) - config.set_tag(new_base) - config.copy(path, new_base + [interface]) - config.delete(path) - - # if "ip ospf" was the only setting, we can clean out the empty - # ip node afterwards - if len(config.list_nodes(path[:-1])) == 0: - config.delete(path[:-1]) - -def migrate_ospfv3(config, path, interface): - path = path + ['ospfv3'] - if config.exists(path): - new_base = ['protocols', 'ospfv3', 'interface'] - config.set(new_base) - config.set_tag(new_base) - config.copy(path, new_base + [interface]) - config.delete(path) - # if "ipv6 ospfv3" was the only setting, we can clean out the empty - # ip node afterwards - if len(config.list_nodes(path[:-1])) == 0: - config.delete(path[:-1]) - -def migrate_rip(config, path, interface): - path = path + ['rip'] - if config.exists(path): - new_base = ['protocols', 'rip', 'interface'] - config.set(new_base) - config.set_tag(new_base) - config.copy(path, new_base + [interface]) - config.delete(path) - - # if "ip rip" was the only setting, we can clean out the empty - # ip node afterwards - if len(config.list_nodes(path[:-1])) == 0: - config.delete(path[:-1]) +from vyos.configtree import ConfigTree -def migrate_ripng(config, path, interface): - path = path + ['ripng'] - if config.exists(path): - new_base = ['protocols', 'ripng', 'interface'] - config.set(new_base) - config.set_tag(new_base) - config.copy(path, new_base + [interface]) - config.delete(path) +def replace_nat_interfaces(config, old, new): + if not config.exists(['nat']): + return + for direction in ['destination', 'source']: + conf_direction = ['nat', direction, 'rule'] + if not config.exists(conf_direction): + return + for rule in config.list_nodes(conf_direction): + conf_rule = conf_direction + [rule] + if config.exists(conf_rule + ['inbound-interface']): + tmp = config.return_value(conf_rule + ['inbound-interface']) + if tmp == old: + config.set(conf_rule + ['inbound-interface'], value=new) + if config.exists(conf_rule + ['outbound-interface']): + tmp = config.return_value(conf_rule + ['outbound-interface']) + if tmp == old: + config.set(conf_rule + ['outbound-interface'], value=new) - # if "ipv6 ripng" was the only setting, we can clean out the empty - # ip node afterwards - if len(config.list_nodes(path[:-1])) == 0: - config.delete(path[:-1]) if __name__ == '__main__': if (len(argv) < 1): @@ -80,62 +46,58 @@ if __name__ == '__main__': exit(1) file_name = argv[1] + with open(file_name, 'r') as f: config_file = f.read() config = ConfigTree(config_file) - - # - # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0" - # - for type in config.list_nodes(['interfaces']): - for interface in config.list_nodes(['interfaces', type]): - ip_base = ['interfaces', type, interface, 'ip'] - ipv6_base = ['interfaces', type, interface, 'ipv6'] - migrate_rip(config, ip_base, interface) - migrate_ripng(config, ipv6_base, interface) - migrate_ospf(config, ip_base, interface) - migrate_ospfv3(config, ipv6_base, interface) - - vif_path = ['interfaces', type, interface, 'vif'] - if config.exists(vif_path): - for vif in config.list_nodes(vif_path): - vif_ip_base = vif_path + [vif, 'ip'] - vif_ipv6_base = vif_path + [vif, 'ipv6'] - ifname = f'{interface}.{vif}' - - migrate_rip(config, vif_ip_base, ifname) - migrate_ripng(config, vif_ipv6_base, ifname) - migrate_ospf(config, vif_ip_base, ifname) - migrate_ospfv3(config, vif_ipv6_base, ifname) - - - vif_s_path = ['interfaces', type, interface, 'vif-s'] - if config.exists(vif_s_path): - for vif_s in config.list_nodes(vif_s_path): - vif_s_ip_base = vif_s_path + [vif_s, 'ip'] - vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6'] - - # vif-c interfaces MUST be migrated before their parent vif-s - # interface as the migrate_*() functions delete the path! - vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] - if config.exists(vif_c_path): - for vif_c in config.list_nodes(vif_c_path): - vif_c_ip_base = vif_c_path + [vif_c, 'ip'] - vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6'] - ifname = f'{interface}.{vif_s}.{vif_c}' - - migrate_rip(config, vif_c_ip_base, ifname) - migrate_ripng(config, vif_c_ipv6_base, ifname) - migrate_ospf(config, vif_c_ip_base, ifname) - migrate_ospfv3(config, vif_c_ipv6_base, ifname) - - - ifname = f'{interface}.{vif_s}' - migrate_rip(config, vif_s_ip_base, ifname) - migrate_ripng(config, vif_s_ipv6_base, ifname) - migrate_ospf(config, vif_s_ip_base, ifname) - migrate_ospfv3(config, vif_s_ipv6_base, ifname) + base = ['interfaces', 'wirelessmodem'] + if not config.exists(base): + # Nothing to do + exit(0) + + new_base = ['interfaces', 'wwan'] + config.set(new_base) + config.set_tag(new_base) + for old_interface in config.list_nodes(base): + # convert usb0b1.3p1.2 device identifier and extract 1.3 usb bus id + usb = config.return_value(base + [old_interface, 'device']) + device = usb.split('b')[-1] + busid = device.split('p')[0] + for new_interface in os.listdir('/sys/class/net'): + # we are only interested in interfaces starting with wwan + if not new_interface.startswith('wwan'): + continue + device = os.readlink(f'/sys/class/net/{new_interface}/device') + device = device.split(':')[0] + if busid in device: + config.copy(base + [old_interface], new_base + [new_interface]) + replace_nat_interfaces(config, old_interface, new_interface) + + config.delete(base) + + # Now that we have copied the old wirelessmodem interfaces to wwan + # we can start to migrate also individual config items. + for interface in config.list_nodes(new_base): + # we do no longer need the USB device name + config.delete(new_base + [interface, 'device']) + # set/unset DNS configuration + dns = new_base + [interface, 'no-peer-dns'] + if config.exists(dns): + config.delete(dns) + else: + config.set(['system', 'name-servers-dhcp'], value=interface, replace=False) + + # Backup distance is now handled by DHCP option "default-route-distance" + distance = dns = new_base + [interface, 'backup', 'distance'] + old_default_distance = '10' + if config.exists(distance): + old_default_distance = config.return_value(distance) + config.delete(distance) + config.set(new_base + [interface, 'dhcp-options', 'default-route-distance'], value=old_default_distance) + + # the new wwan interface use regular IP addressing + config.set(new_base + [interface, 'address'], value='dhcp') try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/interfaces/19-to-20 b/src/migration-scripts/interfaces/19-to-20 index e96663e54..06e07572f 100755 --- a/src/migration-scripts/interfaces/19-to-20 +++ b/src/migration-scripts/interfaces/19-to-20 @@ -18,6 +18,62 @@ from sys import argv from sys import exit from vyos.configtree import ConfigTree +def migrate_ospf(config, path, interface): + path = path + ['ospf'] + if config.exists(path): + new_base = ['protocols', 'ospf', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ip ospf" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + +def migrate_ospfv3(config, path, interface): + path = path + ['ospfv3'] + if config.exists(path): + new_base = ['protocols', 'ospfv3', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ipv6 ospfv3" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + +def migrate_rip(config, path, interface): + path = path + ['rip'] + if config.exists(path): + new_base = ['protocols', 'rip', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ip rip" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + +def migrate_ripng(config, path, interface): + path = path + ['ripng'] + if config.exists(path): + new_base = ['protocols', 'ripng', 'interface'] + config.set(new_base) + config.set_tag(new_base) + config.copy(path, new_base + [interface]) + config.delete(path) + + # if "ipv6 ripng" was the only setting, we can clean out the empty + # ip node afterwards + if len(config.list_nodes(path[:-1])) == 0: + config.delete(path[:-1]) + if __name__ == '__main__': if (len(argv) < 1): print("Must specify file name!") @@ -29,29 +85,57 @@ if __name__ == '__main__': config = ConfigTree(config_file) - for type in ['tunnel', 'l2tpv3']: - base = ['interfaces', type] - if not config.exists(base): - # Nothing to do - continue - - for interface in config.list_nodes(base): - # Migrate "interface tunnel <tunX> encapsulation gre-bridge" to gretap - encap_path = base + [interface, 'encapsulation'] - if type == 'tunnel' and config.exists(encap_path): - tmp = config.return_value(encap_path) - if tmp == 'gre-bridge': - config.set(encap_path, value='gretap') - - # Migrate "interface tunnel|l2tpv3 <interface> local-ip" to source-address - # Migrate "interface tunnel|l2tpv3 <interface> remote-ip" to remote - local_ip_path = base + [interface, 'local-ip'] - if config.exists(local_ip_path): - config.rename(local_ip_path, 'source-address') - - remote_ip_path = base + [interface, 'remote-ip'] - if config.exists(remote_ip_path): - config.rename(remote_ip_path, 'remote') + # + # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0" + # + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + ip_base = ['interfaces', type, interface, 'ip'] + ipv6_base = ['interfaces', type, interface, 'ipv6'] + migrate_rip(config, ip_base, interface) + migrate_ripng(config, ipv6_base, interface) + migrate_ospf(config, ip_base, interface) + migrate_ospfv3(config, ipv6_base, interface) + + vif_path = ['interfaces', type, interface, 'vif'] + if config.exists(vif_path): + for vif in config.list_nodes(vif_path): + vif_ip_base = vif_path + [vif, 'ip'] + vif_ipv6_base = vif_path + [vif, 'ipv6'] + ifname = f'{interface}.{vif}' + + migrate_rip(config, vif_ip_base, ifname) + migrate_ripng(config, vif_ipv6_base, ifname) + migrate_ospf(config, vif_ip_base, ifname) + migrate_ospfv3(config, vif_ipv6_base, ifname) + + + vif_s_path = ['interfaces', type, interface, 'vif-s'] + if config.exists(vif_s_path): + for vif_s in config.list_nodes(vif_s_path): + vif_s_ip_base = vif_s_path + [vif_s, 'ip'] + vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6'] + + # vif-c interfaces MUST be migrated before their parent vif-s + # interface as the migrate_*() functions delete the path! + vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c'] + if config.exists(vif_c_path): + for vif_c in config.list_nodes(vif_c_path): + vif_c_ip_base = vif_c_path + [vif_c, 'ip'] + vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6'] + ifname = f'{interface}.{vif_s}.{vif_c}' + + migrate_rip(config, vif_c_ip_base, ifname) + migrate_ripng(config, vif_c_ipv6_base, ifname) + migrate_ospf(config, vif_c_ip_base, ifname) + migrate_ospfv3(config, vif_c_ipv6_base, ifname) + + + ifname = f'{interface}.{vif_s}' + migrate_rip(config, vif_s_ip_base, ifname) + migrate_ripng(config, vif_s_ipv6_base, ifname) + migrate_ospf(config, vif_s_ip_base, ifname) + migrate_ospfv3(config, vif_s_ipv6_base, ifname) try: with open(file_name, 'w') as f: diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 index d1ec2ad3e..e96663e54 100755 --- a/src/migration-scripts/interfaces/20-to-21 +++ b/src/migration-scripts/interfaces/20-to-21 @@ -14,47 +14,48 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# A VTI interface also requires an IPSec configuration - VyOS 1.2 supported -# having a VTI interface in the CLI but no IPSec configuration - drop VTI -# configuration if this is the case for VyOS 1.4 - -import sys +from sys import argv +from sys import exit from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(sys.argv) < 1): + if (len(argv) < 1): print("Must specify file name!") - sys.exit(1) - - file_name = sys.argv[1] + exit(1) + file_name = argv[1] with open(file_name, 'r') as f: config_file = f.read() config = ConfigTree(config_file) - base = ['interfaces', 'vti'] - if not config.exists(base): - # Nothing to do - sys.exit(0) - - ipsec_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] - for interface in config.list_nodes(base): - found = False - if config.exists(ipsec_base): - for peer in config.list_nodes(ipsec_base): - if config.exists(ipsec_base + [peer, 'vti', 'bind']): - tmp = config.return_value(ipsec_base + [peer, 'vti', 'bind']) - if tmp == interface: - # Interface was found and we no longer need to search - # for it in our IPSec peers - found = True - break - if not found: - config.delete(base + [interface]) + + for type in ['tunnel', 'l2tpv3']: + base = ['interfaces', type] + if not config.exists(base): + # Nothing to do + continue + + for interface in config.list_nodes(base): + # Migrate "interface tunnel <tunX> encapsulation gre-bridge" to gretap + encap_path = base + [interface, 'encapsulation'] + if type == 'tunnel' and config.exists(encap_path): + tmp = config.return_value(encap_path) + if tmp == 'gre-bridge': + config.set(encap_path, value='gretap') + + # Migrate "interface tunnel|l2tpv3 <interface> local-ip" to source-address + # Migrate "interface tunnel|l2tpv3 <interface> remote-ip" to remote + local_ip_path = base + [interface, 'local-ip'] + if config.exists(local_ip_path): + config.rename(local_ip_path, 'source-address') + + remote_ip_path = base + [interface, 'remote-ip'] + if config.exists(remote_ip_path): + config.rename(remote_ip_path, 'remote') 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)) - sys.exit(1) + exit(1) diff --git a/src/migration-scripts/interfaces/21-to-22 b/src/migration-scripts/interfaces/21-to-22 new file mode 100755 index 000000000..d1ec2ad3e --- /dev/null +++ b/src/migration-scripts/interfaces/21-to-22 @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# A VTI interface also requires an IPSec configuration - VyOS 1.2 supported +# having a VTI interface in the CLI but no IPSec configuration - drop VTI +# configuration if this is the case for VyOS 1.4 + +import sys +from vyos.configtree import ConfigTree + +if __name__ == '__main__': + if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + + file_name = sys.argv[1] + + with open(file_name, 'r') as f: + config_file = f.read() + + config = ConfigTree(config_file) + base = ['interfaces', 'vti'] + if not config.exists(base): + # Nothing to do + sys.exit(0) + + ipsec_base = ['vpn', 'ipsec', 'site-to-site', 'peer'] + for interface in config.list_nodes(base): + found = False + if config.exists(ipsec_base): + for peer in config.list_nodes(ipsec_base): + if config.exists(ipsec_base + [peer, 'vti', 'bind']): + tmp = config.return_value(ipsec_base + [peer, 'vti', 'bind']) + if tmp == interface: + # Interface was found and we no longer need to search + # for it in our IPSec peers + found = True + break + if not found: + config.delete(base + [interface]) + + 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)) + sys.exit(1) diff --git a/src/migration-scripts/policy/0-to-1 b/src/migration-scripts/policy/0-to-1 new file mode 100755 index 000000000..7134920ad --- /dev/null +++ b/src/migration-scripts/policy/0-to-1 @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# T3631: route-map: migrate "set extcommunity-rt" and "set extcommunity-soo" +# to "set extcommunity rt|soo" to match FRR syntax + + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['policy', 'route-map'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + + +for route_map in config.list_nodes(base): + if not config.exists(base + [route_map, 'rule']): + continue + for rule in config.list_nodes(base + [route_map, 'rule']): + base_rule = base + [route_map, 'rule', rule] + + if config.exists(base_rule + ['set', 'extcommunity-rt']): + tmp = config.return_value(base_rule + ['set', 'extcommunity-rt']) + config.delete(base_rule + ['set', 'extcommunity-rt']) + config.set(base_rule + ['set', 'extcommunity', 'rt'], value=tmp) + + + if config.exists(base_rule + ['set', 'extcommunity-soo']): + tmp = config.return_value(base_rule + ['set', 'extcommunity-soo']) + config.delete(base_rule + ['set', 'extcommunity-soo']) + config.set(base_rule + ['set', 'extcommunity', 'soo'], value=tmp) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1) diff --git a/src/op_mode/ping.py b/src/op_mode/ping.py index 29b430d53..924a889db 100755 --- a/src/op_mode/ping.py +++ b/src/op_mode/ping.py @@ -50,6 +50,11 @@ options = { 'type': '<seconds>', 'help': 'Number of seconds before ping exits' }, + 'do-not-fragment': { + 'ping': '{command} -M dont', + 'type': 'noarg', + 'help': 'Set DF-bit flag to 1 for no fragmentation' + }, 'flood': { 'ping': 'sudo {command} -f', 'type': 'noarg', @@ -227,4 +232,4 @@ if __name__ == '__main__': # print(f'{command} {host}') os.system(f'{command} {host}') - +
\ No newline at end of file diff --git a/src/op_mode/show_wwan.py b/src/op_mode/show_wwan.py new file mode 100755 index 000000000..249dda2a5 --- /dev/null +++ b/src/op_mode/show_wwan.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse + +from sys import exit +from vyos.util import cmd + +parser = argparse.ArgumentParser() +parser.add_argument("--model", help="Get module model", action="store_true") +parser.add_argument("--revision", help="Get module revision", action="store_true") +parser.add_argument("--capabilities", help="Get module capabilities", action="store_true") +parser.add_argument("--imei", help="Get module IMEI/ESN/MEID", action="store_true") +parser.add_argument("--imsi", help="Get module IMSI", action="store_true") +parser.add_argument("--msisdn", help="Get module MSISDN", action="store_true") +parser.add_argument("--sim", help="Get SIM card status", action="store_true") +parser.add_argument("--signal", help="Get current RF signal info", action="store_true") +parser.add_argument("--firmware", help="Get current RF signal info", action="store_true") + +required = parser.add_argument_group('Required arguments') +required.add_argument("--interface", help="WWAN interface name, e.g. wwan0", required=True) + +def qmi_cmd(device, command, silent=False): + tmp = cmd(f'qmicli --device={device} --device-open-proxy {command}') + tmp = tmp.replace(f'[{cdc}] ', '') + if not silent: + # skip first line as this only holds the info headline + for line in tmp.splitlines()[1:]: + print(line.lstrip()) + return tmp + +if __name__ == '__main__': + args = parser.parse_args() + + # remove the WWAN prefix from the interface, required for the CDC interface + if_num = args.interface.replace('wwan','') + cdc = f'/dev/cdc-wdm{if_num}' + + if args.model: + qmi_cmd(cdc, '--dms-get-model') + elif args.capabilities: + qmi_cmd(cdc, '--dms-get-capabilities') + qmi_cmd(cdc, '--dms-get-band-capabilities') + elif args.revision: + qmi_cmd(cdc, '--dms-get-revision') + elif args.imei: + qmi_cmd(cdc, '--dms-get-ids') + elif args.imsi: + qmi_cmd(cdc, '--dms-uim-get-imsi') + elif args.msisdn: + qmi_cmd(cdc, '--dms-get-msisdn') + elif args.sim: + qmi_cmd(cdc, '--uim-get-card-status') + elif args.signal: + qmi_cmd(cdc, '--nas-get-signal-info') + qmi_cmd(cdc, '--nas-get-rf-band-info') + elif args.firmware: + tmp = qmi_cmd(cdc, '--dms-get-manufacturer', silent=True) + if 'Sierra Wireless' in tmp: + qmi_cmd(cdc, '--dms-swi-get-current-firmware') + else: + qmi_cmd(cdc, '--dms-get-software-version') + else: + parser.print_help() + exit(1) diff --git a/src/op_mode/vpn_ike_sa.py b/src/op_mode/vpn_ike_sa.py index 28da9f8dc..622498a7f 100755 --- a/src/op_mode/vpn_ike_sa.py +++ b/src/op_mode/vpn_ike_sa.py @@ -36,9 +36,9 @@ def ike_sa(peer, nat): peers = [] for conn in sas: for name, sa in conn.items(): - if peer and not name.startswith('peer-' + peer): + if peer and not name.startswith('peer_' + peer): continue - if name.startswith('peer-') and name in peers: + if name.startswith('peer_') and name in peers: continue if nat and 'nat-local' not in sa: continue @@ -49,7 +49,9 @@ def ike_sa(peer, nat): print('%-39s %-39s' % (remote_str, local_str)) state = 'up' if 'state' in sa and s(sa['state']) == 'ESTABLISHED' else 'down' version = 'IKEv' + s(sa['version']) - encryption = f'{s(sa["encr-alg"])}_{s(sa["encr-keysize"])}' if 'encr-alg' in sa else 'n/a' + encryption = f'{s(sa["encr-alg"])}' if 'encr-alg' in sa else 'n/a' + if 'encr-keysize' in sa: + encryption += '_' + s(sa["encr-keysize"]) integrity = s(sa['integ-alg']) if 'integ-alg' in sa else 'n/a' dh_group = s(sa['dh-group']) if 'dh-group' in sa else 'n/a' natt = 'yes' if 'nat-local' in sa and s(sa['nat-local']) == 'yes' else 'no' diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py index 434186abb..582b5ef95 100755 --- a/src/op_mode/vpn_ipsec.py +++ b/src/op_mode/vpn_ipsec.py @@ -100,13 +100,13 @@ def generate_x509_pair(name): print(f'Private key: {X509_PATH}{name}.key') def get_peer_connections(peer, tunnel, return_all = False): - search = rf'^conn (peer-{peer}-(tunnel-[\d]+|vti))$' + search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*' matches = [] - with open(IPSEC_CONF, 'r') as f: + with open(SWANCTL_CONF, 'r') as f: for line in f.readlines(): result = re.match(search, line) if result: - suffix = f'tunnel-{tunnel}' if tunnel.isnumeric() else tunnel + suffix = f'tunnel_{tunnel}' if tunnel.isnumeric() else tunnel if return_all or (result[2] == suffix): matches.append(result[1]) return matches @@ -171,13 +171,14 @@ def debug_peer(peer, tunnel): if not tunnel or tunnel == 'all': tunnel = '' - conn = get_peer_connection(peer, tunnel) + conn = get_peer_connections(peer, tunnel) - if not conn: + if not conns: print('Peer not found, aborting') return - call(f'sudo /usr/sbin/ipsec statusall | grep {conn}') + for conn in conns: + call(f'sudo /usr/sbin/ipsec statusall | grep {conn}') if __name__ == '__main__': parser = argparse.ArgumentParser() diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 8069d7146..cbf321dc8 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -32,6 +32,9 @@ from fastapi.responses import HTMLResponse from fastapi.exceptions import RequestValidationError from fastapi.routing import APIRoute from pydantic import BaseModel, StrictStr, validator +from starlette.datastructures import FormData, MutableHeaders +from starlette.formparsers import FormParser, MultiPartParser +from multipart.multipart import parse_options_header import vyos.config @@ -236,6 +239,35 @@ class MultipartRequest(Request): ERR_PATH_NOT_LIST_OF_STR = False offending_command = {} exception = None + + @property + def orig_headers(self): + self._orig_headers = super().headers + return self._orig_headers + + @property + def headers(self): + self._headers = super().headers.mutablecopy() + self._headers['content-type'] = 'application/json' + return self._headers + + async def form(self) -> FormData: + if not hasattr(self, "_form"): + assert ( + parse_options_header is not None + ), "The `python-multipart` library must be installed to use form parsing." + content_type_header = self.orig_headers.get("Content-Type") + content_type, options = parse_options_header(content_type_header) + if content_type == b"multipart/form-data": + multipart_parser = MultiPartParser(self.orig_headers, self.stream()) + self._form = await multipart_parser.parse() + elif content_type == b"application/x-www-form-urlencoded": + form_parser = FormParser(self.orig_headers, self.stream()) + self._form = await form_parser.parse() + else: + self._form = FormData() + return self._form + async def body(self) -> bytes: if not hasattr(self, "_body"): forms = {} diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index 7e2076820..3b4330e9b 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -37,6 +37,8 @@ logs_handler_syslog.setFormatter(logs_format) logger.addHandler(logs_handler_syslog) logger.setLevel(logging.DEBUG) +mdns_running_file = '/run/mdns_vrrp_active' +mdns_update_command = 'sudo /usr/libexec/vyos/conf_mode/service_mdns-repeater.py' # class for all operations class KeepalivedFifo: @@ -121,6 +123,9 @@ class KeepalivedFifo: logger.info("{} {} changed state to {}".format(n_type, n_name, n_state)) # check and run commands for VRRP instances if n_type == 'INSTANCE': + if os.path.exists(mdns_running_file): + cmd(mdns_update_command) + if n_name in self.vrrp_config['vrrp_groups'] and n_state in self.vrrp_config['vrrp_groups'][n_name]: n_script = self.vrrp_config['vrrp_groups'][n_name].get(n_state) if n_script: @@ -128,6 +133,9 @@ class KeepalivedFifo: # check and run commands for VRRP sync groups # currently, this is not available in VyOS CLI if n_type == 'GROUP': + if os.path.exists(mdns_running_file): + cmd(mdns_update_command) + if n_name in self.vrrp_config['sync_groups'] and n_state in self.vrrp_config['sync_groups'][n_name]: n_script = self.vrrp_config['sync_groups'][n_name].get(n_state) if n_script: diff --git a/src/validators/interface-name b/src/validators/interface-name index 5bac671b1..105815eee 100755 --- a/src/validators/interface-name +++ b/src/validators/interface-name @@ -20,7 +20,7 @@ import re from sys import argv from sys import exit -pattern = '^(bond|br|dum|en|ersp|eth|gnv|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|vti|vtun|vxlan|wg|wlan|wlm)[0-9]+(.\d+)?|lo$' +pattern = '^(bond|br|dum|en|ersp|eth|gnv|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|vti|vtun|vxlan|wg|wlan|wwan)[0-9]+(.\d+)?|lo$' if __name__ == '__main__': if len(argv) != 2: diff --git a/src/validators/vrf-name b/src/validators/vrf-name index 7b6313888..c78a80776 100755 --- a/src/validators/vrf-name +++ b/src/validators/vrf-name @@ -34,7 +34,7 @@ if __name__ == '__main__': exit(1) pattern = "^(?!(bond|br|dum|eth|lan|eno|ens|enp|enx|gnv|ipoe|l2tp|l2tpeth|" \ - "vtun|ppp|pppoe|peth|tun|vti|vxlan|wg|wlan|wlm)\d+(\.\d+(v.+)?)?$).*$" + "vtun|ppp|pppoe|peth|tun|vti|vxlan|wg|wlan|wwan)\d+(\.\d+(v.+)?)?$).*$" if not re.match(pattern, vrf): exit(1) |