summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/trigger-pr-mirror-repo-sync.yml5
-rw-r--r--data/templates/container/containers.conf.j24
-rw-r--r--data/templates/dhcp-client/ipv6.override.conf.j23
-rw-r--r--data/templates/dhcp-server/kea-ctrl-agent.conf.j214
-rw-r--r--data/templates/dhcp-server/kea-dhcp-ddns.conf.j230
-rw-r--r--data/templates/dhcp-server/kea-dhcp4.conf.j237
-rw-r--r--data/templates/firewall/nftables-geoip-update.j233
-rw-r--r--data/templates/firewall/nftables-policy.j217
-rwxr-xr-xdata/templates/firewall/nftables.j236
-rw-r--r--data/templates/frr/bgpd.frr.j22
-rw-r--r--data/templates/ids/fastnetmon.j2121
-rw-r--r--data/templates/ids/fastnetmon_excluded_networks_list.j25
-rw-r--r--data/templates/ids/fastnetmon_networks_list.j25
-rw-r--r--data/templates/ipsec/swanctl/peer.j215
-rw-r--r--data/templates/prometheus/node_exporter.service.j211
-rw-r--r--data/templates/router-advert/radvd.conf.j27
-rw-r--r--debian/control3
-rw-r--r--debian/vyos-1x.links1
-rw-r--r--debian/vyos-1x.postinst2
-rw-r--r--interface-definitions/container.xml.in19
-rw-r--r--interface-definitions/include/dhcp/ddns-dns-server.xml.i19
-rw-r--r--interface-definitions/include/dhcp/ddns-settings.xml.i172
-rw-r--r--interface-definitions/include/dhcp/option-v4.xml.i12
-rw-r--r--interface-definitions/include/dhcp/option-v6.xml.i12
-rw-r--r--interface-definitions/include/dhcp/ping-check.xml.i8
-rw-r--r--interface-definitions/include/firewall/global-options.xml.i8
-rw-r--r--interface-definitions/include/interface/ipv6-address-interface-identifier.xml.i15
-rw-r--r--interface-definitions/include/interface/ipv6-address.xml.i12
-rw-r--r--interface-definitions/include/interface/ipv6-options-with-nd.xml.i9
-rw-r--r--interface-definitions/include/interface/ipv6-options.xml.i11
-rw-r--r--interface-definitions/include/interface/vif-s.xml.i2
-rw-r--r--interface-definitions/include/interface/vif.xml.i1
-rw-r--r--interface-definitions/include/version/ids-version.xml.i2
-rw-r--r--interface-definitions/interfaces_bonding.xml.in1
-rw-r--r--interface-definitions/interfaces_bridge.xml.in1
-rw-r--r--interface-definitions/interfaces_ethernet.xml.in1
-rw-r--r--interface-definitions/interfaces_geneve.xml.in1
-rw-r--r--interface-definitions/interfaces_l2tpv3.xml.in1
-rw-r--r--interface-definitions/interfaces_macsec.xml.in1
-rw-r--r--interface-definitions/interfaces_openvpn.xml.in1
-rw-r--r--interface-definitions/interfaces_pppoe.xml.in1
-rw-r--r--interface-definitions/interfaces_pseudo-ethernet.xml.in1
-rw-r--r--interface-definitions/interfaces_vxlan.xml.in1
-rw-r--r--interface-definitions/interfaces_wireless.xml.in1
-rw-r--r--interface-definitions/interfaces_wwan.xml.in1
-rw-r--r--interface-definitions/policy_route.xml.in4
-rw-r--r--interface-definitions/service_dhcp-server.xml.in123
-rw-r--r--interface-definitions/service_ids_ddos-protection.xml.in167
-rw-r--r--interface-definitions/service_router-advert.xml.in13
-rw-r--r--interface-definitions/system_option.xml.in6
-rw-r--r--interface-definitions/vpn_ipsec.xml.in57
m---------libvyosconfig0
-rwxr-xr-xop-mode-definitions/firewall.xml.in7
-rw-r--r--op-mode-definitions/monitor-log.xml.in13
-rw-r--r--op-mode-definitions/reset-session.xml.in (renamed from op-mode-definitions/clear-session.xml.in)2
-rwxr-xr-xop-mode-definitions/show-log.xml.in48
-rw-r--r--op-mode-definitions/system-image.xml.in2
-rw-r--r--python/vyos/component_version.py63
-rw-r--r--python/vyos/configdict.py25
-rw-r--r--python/vyos/configsession.py55
-rw-r--r--python/vyos/configverify.py6
-rw-r--r--python/vyos/defaults.py2
-rwxr-xr-xpython/vyos/firewall.py67
-rw-r--r--python/vyos/frrender.py2
-rw-r--r--python/vyos/ifconfig/bridge.py14
-rw-r--r--python/vyos/ifconfig/interface.py126
-rw-r--r--python/vyos/kea.py67
-rw-r--r--python/vyos/proto/vyconf_client.py4
-rw-r--r--python/vyos/system/grub_util.py5
-rwxr-xr-xpython/vyos/template.py98
-rw-r--r--python/vyos/vyconf_session.py123
-rwxr-xr-xscripts/build-command-op-templates2
-rw-r--r--smoketest/config-tests/basic-vyos17
-rw-r--r--smoketest/configs/basic-vyos60
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py12
-rwxr-xr-xsmoketest/scripts/cli/test_container.py156
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py7
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_vxlan.py14
-rwxr-xr-xsmoketest/scripts/cli/test_policy_route.py34
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-server.py196
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcpv6-server.py5
-rwxr-xr-xsmoketest/scripts/cli/test_service_https.py24
-rwxr-xr-xsmoketest/scripts/cli/test_service_ids_ddos-protection.py116
-rwxr-xr-xsmoketest/scripts/cli/test_service_router-advert.py43
-rwxr-xr-xsmoketest/scripts/cli/test_system_option.py32
-rwxr-xr-xsmoketest/scripts/cli/test_system_syslog.py6
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_ipsec.py88
-rwxr-xr-xsmoketest/scripts/system/test_kernel_options.py9
-rwxr-xr-xsrc/conf_mode/firewall.py26
-rwxr-xr-xsrc/conf_mode/interfaces_bridge.py2
-rwxr-xr-xsrc/conf_mode/interfaces_pseudo-ethernet.py2
-rwxr-xr-xsrc/conf_mode/interfaces_virtual-ethernet.py2
-rwxr-xr-xsrc/conf_mode/interfaces_vti.py2
-rwxr-xr-xsrc/conf_mode/interfaces_wwan.py2
-rwxr-xr-xsrc/conf_mode/policy_route.py47
-rwxr-xr-xsrc/conf_mode/service_dhcp-server.py47
-rwxr-xr-xsrc/conf_mode/service_https.py14
-rwxr-xr-xsrc/conf_mode/service_ids_ddos-protection.py104
-rwxr-xr-xsrc/conf_mode/system_host-name.py2
-rwxr-xr-xsrc/conf_mode/system_option.py11
-rwxr-xr-xsrc/conf_mode/system_syslog.py2
-rw-r--r--src/etc/dhcp/dhclient-enter-hooks.d/06-vyos-nodefaultroute20
-rwxr-xr-xsrc/etc/netplug/vyos-netplug-dhcp-client39
-rw-r--r--src/etc/sysctl.d/30-vyos-router.conf10
-rw-r--r--src/etc/systemd/system/fastnetmon.service.d/override.conf12
-rw-r--r--src/etc/systemd/system/kea-ctrl-agent.service.d/override.conf10
-rw-r--r--src/etc/systemd/system/kea-dhcp-ddns-server.service.d/override.conf7
-rwxr-xr-xsrc/helpers/geoip-update.py17
-rwxr-xr-xsrc/init/vyos-router15
-rw-r--r--src/migration-scripts/dhcp-server/7-to-86
-rw-r--r--src/migration-scripts/ids/1-to-230
-rwxr-xr-xsrc/op_mode/firewall.py3
-rwxr-xr-xsrc/op_mode/image_installer.py48
-rw-r--r--src/op_mode/tech_support.py10
-rwxr-xr-xsrc/services/vyos-domain-resolver8
-rw-r--r--src/systemd/netplug.service9
-rw-r--r--src/systemd/vyos.target2
117 files changed, 2238 insertions, 856 deletions
diff --git a/.github/workflows/trigger-pr-mirror-repo-sync.yml b/.github/workflows/trigger-pr-mirror-repo-sync.yml
index f74895987..978be0582 100644
--- a/.github/workflows/trigger-pr-mirror-repo-sync.yml
+++ b/.github/workflows/trigger-pr-mirror-repo-sync.yml
@@ -6,6 +6,11 @@ on:
branches:
- current
+permissions:
+ pull-requests: write
+ contents: write
+ issues: write
+
jobs:
call-trigger-mirror-pr-repo-sync:
if: github.repository_owner == 'vyos' && github.event.pull_request.merged == true
diff --git a/data/templates/container/containers.conf.j2 b/data/templates/container/containers.conf.j2
index c8b54dfbb..65436801e 100644
--- a/data/templates/container/containers.conf.j2
+++ b/data/templates/container/containers.conf.j2
@@ -172,7 +172,11 @@ default_sysctls = [
# Logging driver for the container. Available options: k8s-file and journald.
#
+{% if log_driver is vyos_defined %}
+log_driver = "{{ log_driver }}"
+{% else %}
#log_driver = "k8s-file"
+{% endif %}
# Maximum size allowed for the container log file. Negative numbers indicate
# that no size limit is imposed. If positive, it must be >= 8192 to match or
diff --git a/data/templates/dhcp-client/ipv6.override.conf.j2 b/data/templates/dhcp-client/ipv6.override.conf.j2
index b0c0e0544..d270a55fc 100644
--- a/data/templates/dhcp-client/ipv6.override.conf.j2
+++ b/data/templates/dhcp-client/ipv6.override.conf.j2
@@ -4,6 +4,9 @@
[Unit]
ConditionPathExists={{ dhcp6_client_dir }}/dhcp6c.%i.conf
+{% if ifname.startswith('pppoe') %}
+After=ppp@{{ ifname }}.service
+{% endif %}
[Service]
ExecStart=
diff --git a/data/templates/dhcp-server/kea-ctrl-agent.conf.j2 b/data/templates/dhcp-server/kea-ctrl-agent.conf.j2
deleted file mode 100644
index b37cf4798..000000000
--- a/data/templates/dhcp-server/kea-ctrl-agent.conf.j2
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "Control-agent": {
-{% if high_availability is vyos_defined %}
- "http-host": "{{ high_availability.source_address }}",
- "http-port": 647,
- "control-sockets": {
- "dhcp4": {
- "socket-type": "unix",
- "socket-name": "/run/kea/dhcp4-ctrl-socket"
- }
- }
-{% endif %}
- }
-}
diff --git a/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 b/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2
new file mode 100644
index 000000000..7b0394a88
--- /dev/null
+++ b/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2
@@ -0,0 +1,30 @@
+{
+ "DhcpDdns": {
+ "ip-address": "127.0.0.1",
+ "port": 53001,
+ "control-socket": {
+ "socket-type": "unix",
+ "socket-name": "/run/kea/kea-ddns-ctrl-socket"
+ },
+ "tsig-keys": {{ dynamic_dns_update | kea_dynamic_dns_update_tsig_key_json }},
+ "forward-ddns" : {
+ "ddns-domains": {{ dynamic_dns_update | kea_dynamic_dns_update_domains('forward_domain') }}
+ },
+ "reverse-ddns" : {
+ "ddns-domains": {{ dynamic_dns_update | kea_dynamic_dns_update_domains('reverse_domain') }}
+ },
+ "loggers": [
+ {
+ "name": "kea-dhcp-ddns",
+ "output_options": [
+ {
+ "output": "stdout",
+ "pattern": "%-5p %m\n"
+ }
+ ],
+ "severity": "INFO",
+ "debuglevel": 0
+ }
+ ]
+ }
+}
diff --git a/data/templates/dhcp-server/kea-dhcp4.conf.j2 b/data/templates/dhcp-server/kea-dhcp4.conf.j2
index 2e10d58e0..d08ca0eaa 100644
--- a/data/templates/dhcp-server/kea-dhcp4.conf.j2
+++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2
@@ -25,20 +25,6 @@
},
"option-def": [
{
- "name": "rfc3442-static-route",
- "code": 121,
- "type": "record",
- "array": true,
- "record-types": "uint8,uint8,uint8,uint8,uint8,uint8,uint8"
- },
- {
- "name": "windows-static-route",
- "code": 249,
- "type": "record",
- "array": true,
- "record-types": "uint8,uint8,uint8,uint8,uint8,uint8,uint8"
- },
- {
"name": "wpad-url",
"code": 252,
"type": "string"
@@ -50,6 +36,19 @@
"space": "ubnt"
}
],
+{% if dynamic_dns_update is vyos_defined %}
+ "dhcp-ddns": {
+ "enable-updates": true,
+ "server-ip": "127.0.0.1",
+ "server-port": 53001,
+ "sender-ip": "",
+ "sender-port": 0,
+ "max-queue-size": 1024,
+ "ncr-protocol": "UDP",
+ "ncr-format": "JSON"
+ },
+ {{ dynamic_dns_update | kea_dynamic_dns_update_main_json }}
+{% endif %}
"hooks-libraries": [
{% if high_availability is vyos_defined %}
{
@@ -69,6 +68,16 @@
},
{% endif %}
{
+ "library": "/usr/lib/{{ machine }}-linux-gnu/kea/hooks/libdhcp_ping_check.so",
+ "parameters": {
+ "enable-ping-check" : false,
+ "min-ping-requests" : 1,
+ "reply-timeout" : 100,
+ "ping-cltt-secs" : 60,
+ "ping-channel-threads" : 0
+ }
+ },
+ {
"library": "/usr/lib/{{ machine }}-linux-gnu/kea/hooks/libdhcp_lease_cmds.so",
"parameters": {}
}
diff --git a/data/templates/firewall/nftables-geoip-update.j2 b/data/templates/firewall/nftables-geoip-update.j2
index 832ccc3e9..d8f80d1f5 100644
--- a/data/templates/firewall/nftables-geoip-update.j2
+++ b/data/templates/firewall/nftables-geoip-update.j2
@@ -31,3 +31,36 @@ table ip6 vyos_filter {
{% endfor %}
}
{% endif %}
+
+
+{% if ipv4_sets_policy is vyos_defined %}
+{% for setname, ip_list in ipv4_sets_policy.items() %}
+flush set ip vyos_mangle {{ setname }}
+{% endfor %}
+
+table ip vyos_mangle {
+{% for setname, ip_list in ipv4_sets_policy.items() %}
+ set {{ setname }} {
+ type ipv4_addr
+ flags interval
+ elements = { {{ ','.join(ip_list) }} }
+ }
+{% endfor %}
+}
+{% endif %}
+
+{% if ipv6_sets_policy is vyos_defined %}
+{% for setname, ip_list in ipv6_sets_policy.items() %}
+flush set ip6 vyos_mangle {{ setname }}
+{% endfor %}
+
+table ip6 vyos_mangle {
+{% for setname, ip_list in ipv6_sets_policy.items() %}
+ set {{ setname }} {
+ type ipv6_addr
+ flags interval
+ elements = { {{ ','.join(ip_list) }} }
+ }
+{% endfor %}
+}
+{% endif %}
diff --git a/data/templates/firewall/nftables-policy.j2 b/data/templates/firewall/nftables-policy.j2
index 9e28899b0..00d0e8a62 100644
--- a/data/templates/firewall/nftables-policy.j2
+++ b/data/templates/firewall/nftables-policy.j2
@@ -33,6 +33,15 @@ table ip vyos_mangle {
{% endif %}
}
{% endfor %}
+
+{% if geoip_updated.name is vyos_defined %}
+{% for setname in geoip_updated.name %}
+ set {{ setname }} {
+ type ipv4_addr
+ flags interval
+ }
+{% endfor %}
+{% endif %}
{% endif %}
{{ group_tmpl.groups(firewall_group, False, True) }}
@@ -65,6 +74,14 @@ table ip6 vyos_mangle {
{% endif %}
}
{% endfor %}
+{% if geoip_updated.ipv6_name is vyos_defined %}
+{% for setname in geoip_updated.ipv6_name %}
+ set {{ setname }} {
+ type ipv6_addr
+ flags interval
+ }
+{% endfor %}
+{% endif %}
{% endif %}
{{ group_tmpl.groups(firewall_group, True, True) }}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 67473da8e..a78119a80 100755
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -47,7 +47,7 @@ table ip vyos_filter {
chain VYOS_FORWARD_{{ prior }} {
type filter hook forward priority {{ prior }}; policy accept;
{% if global_options.state_policy is vyos_defined %}
- jump VYOS_STATE_POLICY
+ jump VYOS_STATE_POLICY_FORWARD
{% endif %}
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
@@ -180,6 +180,22 @@ table ip vyos_filter {
{% endif %}
return
}
+
+ chain VYOS_STATE_POLICY_FORWARD {
+{% if global_options.state_policy.offload is vyos_defined %}
+ counter flow add @VYOS_FLOWTABLE_{{ global_options.state_policy.offload.offload_target }}
+{% endif %}
+{% if global_options.state_policy.established is vyos_defined %}
+ {{ global_options.state_policy.established | nft_state_policy('established') }}
+{% endif %}
+{% if global_options.state_policy.invalid is vyos_defined %}
+ {{ global_options.state_policy.invalid | nft_state_policy('invalid') }}
+{% endif %}
+{% if global_options.state_policy.related is vyos_defined %}
+ {{ global_options.state_policy.related | nft_state_policy('related') }}
+{% endif %}
+ return
+ }
{% endif %}
}
@@ -200,7 +216,7 @@ table ip6 vyos_filter {
chain VYOS_IPV6_FORWARD_{{ prior }} {
type filter hook forward priority {{ prior }}; policy accept;
{% if global_options.state_policy is vyos_defined %}
- jump VYOS_STATE_POLICY6
+ jump VYOS_STATE_POLICY6_FORWARD
{% endif %}
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
@@ -331,6 +347,22 @@ table ip6 vyos_filter {
{% endif %}
return
}
+
+ chain VYOS_STATE_POLICY6_FORWARD {
+{% if global_options.state_policy.offload is vyos_defined %}
+ counter flow add @VYOS_FLOWTABLE_{{ global_options.state_policy.offload.offload_target }}
+{% endif %}
+{% if global_options.state_policy.established is vyos_defined %}
+ {{ global_options.state_policy.established | nft_state_policy('established') }}
+{% endif %}
+{% if global_options.state_policy.invalid is vyos_defined %}
+ {{ global_options.state_policy.invalid | nft_state_policy('invalid') }}
+{% endif %}
+{% if global_options.state_policy.related is vyos_defined %}
+ {{ global_options.state_policy.related | nft_state_policy('related') }}
+{% endif %}
+ return
+ }
{% endif %}
}
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index b89f15be1..e153dd4e8 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -98,6 +98,8 @@
{% endif %}
{% if config.enforce_first_as is vyos_defined %}
neighbor {{ neighbor }} enforce-first-as
+{% else %}
+ no neighbor {{ neighbor }} enforce-first-as
{% endif %}
{% if config.strict_capability_match is vyos_defined %}
neighbor {{ neighbor }} strict-capability-match
diff --git a/data/templates/ids/fastnetmon.j2 b/data/templates/ids/fastnetmon.j2
deleted file mode 100644
index f6f03d0db..000000000
--- a/data/templates/ids/fastnetmon.j2
+++ /dev/null
@@ -1,121 +0,0 @@
-# enable this option if you want to send logs to local syslog facility
-logging:logging_level = debug
-logging:local_syslog_logging = on
-
-# list of all your networks in CIDR format
-networks_list_path = /run/fastnetmon/networks_list
-
-# list networks in CIDR format which will be not monitored for attacks
-white_list_path = /run/fastnetmon/excluded_networks_list
-
-# Enable/Disable any actions in case of attack
-enable_ban = on
-enable_ban_ipv6 = on
-
-## How many packets will be collected from attack traffic
-ban_details_records_count = 500
-
-## How long (in seconds) we should keep an IP in blocked state
-## If you set 0 here it completely disables unban capability
-{% if ban_time is vyos_defined %}
-ban_time = {{ ban_time }}
-{% endif %}
-
-# Check if the attack is still active, before triggering an unban callback with this option
-# If the attack is still active, check each run of the unban watchdog
-unban_only_if_attack_finished = on
-
-# enable per subnet speed meters
-# For each subnet, list track speed in bps and pps for both directions
-enable_subnet_counters = off
-
-{% if mode is vyos_defined('mirror') %}
-mirror_afpacket = on
-{% elif mode is vyos_defined('sflow') %}
-sflow = on
-{% if sflow.port is vyos_defined %}
-sflow_port = {{ sflow.port }}
-{% endif %}
-{% if sflow.listen_address is vyos_defined %}
-sflow_host = {{ sflow.listen_address }}
-{% endif %}
-{% endif %}
-
-
-process_incoming_traffic = {{ 'on' if direction is vyos_defined and 'in' in direction else 'off' }}
-process_outgoing_traffic = {{ 'on' if direction is vyos_defined and 'out' in direction else 'off' }}
-
-{% if threshold is vyos_defined %}
-{% if threshold.general is vyos_defined %}
-# General threshold
-{% for thr, thr_value in threshold.general.items() %}
-{% if thr is vyos_defined('fps') %}
-ban_for_flows = on
-threshold_flows = {{ thr_value }}
-{% elif thr is vyos_defined('mbps') %}
-ban_for_bandwidth = on
-threshold_mbps = {{ thr_value }}
-{% elif thr is vyos_defined('pps') %}
-ban_for_pps = on
-threshold_pps = {{ thr_value }}
-{% endif %}
-{% endfor %}
-{% endif %}
-
-{% if threshold.tcp is vyos_defined %}
-# TCP threshold
-{% for thr, thr_value in threshold.tcp.items() %}
-{% if thr is vyos_defined('fps') %}
-ban_for_tcp_flows = on
-threshold_tcp_flows = {{ thr_value }}
-{% elif thr is vyos_defined('mbps') %}
-ban_for_tcp_bandwidth = on
-threshold_tcp_mbps = {{ thr_value }}
-{% elif thr is vyos_defined('pps') %}
-ban_for_tcp_pps = on
-threshold_tcp_pps = {{ thr_value }}
-{% endif %}
-{% endfor %}
-{% endif %}
-
-{% if threshold.udp is vyos_defined %}
-# UDP threshold
-{% for thr, thr_value in threshold.udp.items() %}
-{% if thr is vyos_defined('fps') %}
-ban_for_udp_flows = on
-threshold_udp_flows = {{ thr_value }}
-{% elif thr is vyos_defined('mbps') %}
-ban_for_udp_bandwidth = on
-threshold_udp_mbps = {{ thr_value }}
-{% elif thr is vyos_defined('pps') %}
-ban_for_udp_pps = on
-threshold_udp_pps = {{ thr_value }}
-{% endif %}
-{% endfor %}
-{% endif %}
-
-{% if threshold.icmp is vyos_defined %}
-# ICMP threshold
-{% for thr, thr_value in threshold.icmp.items() %}
-{% if thr is vyos_defined('fps') %}
-ban_for_icmp_flows = on
-threshold_icmp_flows = {{ thr_value }}
-{% elif thr is vyos_defined('mbps') %}
-ban_for_icmp_bandwidth = on
-threshold_icmp_mbps = {{ thr_value }}
-{% elif thr is vyos_defined('pps') %}
-ban_for_icmp_pps = on
-threshold_icmp_pps = {{ thr_value }}
-{% endif %}
-{% endfor %}
-{% endif %}
-
-{% endif %}
-
-{% if listen_interface is vyos_defined %}
-interfaces = {{ listen_interface | join(',') }}
-{% endif %}
-
-{% if alert_script is vyos_defined %}
-notify_script_path = {{ alert_script }}
-{% endif %}
diff --git a/data/templates/ids/fastnetmon_excluded_networks_list.j2 b/data/templates/ids/fastnetmon_excluded_networks_list.j2
deleted file mode 100644
index c88a1c527..000000000
--- a/data/templates/ids/fastnetmon_excluded_networks_list.j2
+++ /dev/null
@@ -1,5 +0,0 @@
-{% if excluded_network is vyos_defined %}
-{% for net in excluded_network %}
-{{ net }}
-{% endfor %}
-{% endif %}
diff --git a/data/templates/ids/fastnetmon_networks_list.j2 b/data/templates/ids/fastnetmon_networks_list.j2
deleted file mode 100644
index 0a0576d2a..000000000
--- a/data/templates/ids/fastnetmon_networks_list.j2
+++ /dev/null
@@ -1,5 +0,0 @@
-{% if network is vyos_defined %}
-{% for net in network %}
-{{ net }}
-{% endfor %}
-{% endif %}
diff --git a/data/templates/ipsec/swanctl/peer.j2 b/data/templates/ipsec/swanctl/peer.j2
index 3a9af2c94..cf0865c88 100644
--- a/data/templates/ipsec/swanctl/peer.j2
+++ b/data/templates/ipsec/swanctl/peer.j2
@@ -68,8 +68,19 @@
rekey_packets = 0
rekey_time = 0s
{% endif %}
- local_ts = 0.0.0.0/0,::/0
- remote_ts = 0.0.0.0/0,::/0
+{# set default traffic-selectors #}
+{% set local_ts = '0.0.0.0/0,::/0' %}
+{% set remote_ts = '0.0.0.0/0,::/0' %}
+{% if peer_conf.vti.traffic_selector is vyos_defined %}
+{% if peer_conf.vti.traffic_selector.local is vyos_defined and peer_conf.vti.traffic_selector.local.prefix is vyos_defined %}
+{% set local_ts = peer_conf.vti.traffic_selector.local.prefix | join(',') %}
+{% endif %}
+{% if peer_conf.vti.traffic_selector.remote is vyos_defined and peer_conf.vti.traffic_selector.remote.prefix is vyos_defined %}
+{% set remote_ts = peer_conf.vti.traffic_selector.remote.prefix | join(',') %}
+{% endif %}
+{% endif %}
+ local_ts = {{ local_ts }}
+ remote_ts = {{ remote_ts }}
updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }}"
{# The key defaults to 0 and will match any policies which similarly do not have a lookup key configuration. #}
{# Thus we simply shift the key by one to also support a vti0 interface #}
diff --git a/data/templates/prometheus/node_exporter.service.j2 b/data/templates/prometheus/node_exporter.service.j2
index 135439bd6..9a943cd75 100644
--- a/data/templates/prometheus/node_exporter.service.j2
+++ b/data/templates/prometheus/node_exporter.service.j2
@@ -9,6 +9,11 @@ After=network.target
User=node_exporter
{% endif %}
ExecStart={{ vrf_command }}/usr/sbin/node_exporter \
+{% if collectors is vyos_defined %}
+{% if collectors.textfile is vyos_defined %}
+ --collector.textfile.directory=/run/node_exporter/collector \
+{% endif %}
+{% endif %}
{% if listen_address is vyos_defined %}
{% for address in listen_address %}
--web.listen-address={{ address }}:{{ port }}
@@ -16,10 +21,6 @@ ExecStart={{ vrf_command }}/usr/sbin/node_exporter \
{% else %}
--web.listen-address=:{{ port }}
{% endif %}
-{% if collectors is vyos_defined %}
-{% if collectors.textfile is vyos_defined %}
- --collector.textfile.directory=/run/node_exporter/collector
-{% endif %}
-{% endif %}
+
[Install]
WantedBy=multi-user.target
diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2
index a83bd03ac..e37cfde6c 100644
--- a/data/templates/router-advert/radvd.conf.j2
+++ b/data/templates/router-advert/radvd.conf.j2
@@ -57,6 +57,13 @@ interface {{ iface }} {
};
{% endfor %}
{% endif %}
+{% if iface_config.auto_ignore is vyos_defined %}
+ autoignoreprefixes {
+{% for auto_ignore_prefix in iface_config.auto_ignore %}
+ {{ auto_ignore_prefix }};
+{% endfor %}
+ };
+{% endif %}
{% if iface_config.prefix is vyos_defined %}
{% for prefix, prefix_options in iface_config.prefix.items() %}
prefix {{ prefix }} {
diff --git a/debian/control b/debian/control
index c40b0fb04..ec3147820 100644
--- a/debian/control
+++ b/debian/control
@@ -196,7 +196,6 @@ Depends:
ddclient (>= 3.11.1),
# End "service dns dynamic"
# # For "service ids"
- fastnetmon [amd64],
suricata,
suricata-update,
# End "service ids"
@@ -204,7 +203,7 @@ Depends:
ndppd,
# End "service ndp-proxy"
# For "service router-advert"
- radvd,
+ radvd (>= 2.20),
# End "service route-advert"
# For "load-balancing haproxy"
haproxy,
diff --git a/debian/vyos-1x.links b/debian/vyos-1x.links
index 7e21f294c..402c91306 100644
--- a/debian/vyos-1x.links
+++ b/debian/vyos-1x.links
@@ -1,3 +1,2 @@
/etc/netplug/linkup.d/vyos-python-helper /etc/netplug/linkdown.d/vyos-python-helper
/usr/libexec/vyos/system/standalone_root_pw_reset /opt/vyatta/sbin/standalone_root_pw_reset
-/lib/systemd/system/rsyslog.service /etc/systemd/system/syslog.service
diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst
index fde58651a..798ecaa1b 100644
--- a/debian/vyos-1x.postinst
+++ b/debian/vyos-1x.postinst
@@ -221,11 +221,9 @@ fi
# Remove unwanted daemon files from /etc
# conntackd
# pmacct
-# fastnetmon
# ntp
DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd
/etc/default/pmacctd /etc/pmacct
- /etc/networks_list /etc/networks_whitelist /etc/fastnetmon.conf
/etc/ntp.conf /etc/default/ssh /etc/avahi/avahi-daemon.conf /etc/avahi/hosts
/etc/powerdns /etc/default/pdns-recursor
/etc/ppp/ip-up.d/0000usepeerdns /etc/ppp/ip-down.d/0000usepeerdns"
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index 3a5cfbaa6..2682c1b36 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -621,6 +621,25 @@
</node>
</children>
</tagNode>
+ <leafNode name="log-driver">
+ <properties>
+ <help>Configure container log driver</help>
+ <completionHelp>
+ <list>k8s-file journald</list>
+ </completionHelp>
+ <valueHelp>
+ <format>k8s-file</format>
+ <description>Logs to plain-text json file</description>
+ </valueHelp>
+ <valueHelp>
+ <format>journald</format>
+ <description>Logs to systemd's journal</description>
+ </valueHelp>
+ <constraint>
+ <regex>(k8s-file|journald)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
</interfaceDefinition>
diff --git a/interface-definitions/include/dhcp/ddns-dns-server.xml.i b/interface-definitions/include/dhcp/ddns-dns-server.xml.i
new file mode 100644
index 000000000..ba9f186d0
--- /dev/null
+++ b/interface-definitions/include/dhcp/ddns-dns-server.xml.i
@@ -0,0 +1,19 @@
+<!-- include start from dhcp/ddns-dns-server.xml.i -->
+<tagNode name="dns-server">
+ <properties>
+ <help>DNS server specification</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this DNS server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>DNS server number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/address-ipv4-ipv6-single.xml.i>
+ #include <include/port-number.xml.i>
+ </children>
+</tagNode>
+<!-- include end -->
diff --git a/interface-definitions/include/dhcp/ddns-settings.xml.i b/interface-definitions/include/dhcp/ddns-settings.xml.i
new file mode 100644
index 000000000..3e202685e
--- /dev/null
+++ b/interface-definitions/include/dhcp/ddns-settings.xml.i
@@ -0,0 +1,172 @@
+<!-- include start from dhcp/ddns-settings.xml.i -->
+<leafNode name="send-updates">
+ <properties>
+ <help>Enable or disable updates for this scope</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable updates for this scope</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable updates for this scope</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ <constraintErrorMessage>Set it to either enable or disable</constraintErrorMessage>
+ </properties>
+</leafNode>
+<leafNode name="override-client-update">
+ <properties>
+ <help>Always update both forward and reverse DNS data, regardless of the client's request</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Force update both forward and reverse DNS records</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Respect client request settings</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ <constraintErrorMessage>Set it to either enable or disable</constraintErrorMessage>
+ </properties>
+</leafNode>
+<leafNode name="override-no-update">
+ <properties>
+ <help>Perform a DDNS update, even if the client instructs the server not to</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Force DDNS updates regardless of client request</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Respect client request settings</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ <constraintErrorMessage>Set it to either enable or disable</constraintErrorMessage>
+ </properties>
+</leafNode>
+<leafNode name="replace-client-name">
+ <properties>
+ <help>Replace client name mode</help>
+ <completionHelp>
+ <list>never always when-present when-not-present</list>
+ </completionHelp>
+ <valueHelp>
+ <format>never</format>
+ <description>Use the name the client sent. If the client sent no name, do not generate
+ one</description>
+ </valueHelp>
+ <valueHelp>
+ <format>always</format>
+ <description>Replace the name the client sent. If the client sent no name, generate one
+ for the client</description>
+ </valueHelp>
+ <valueHelp>
+ <format>when-present</format>
+ <description>Replace the name the client sent. If the client sent no name, do not
+ generate one</description>
+ </valueHelp>
+ <valueHelp>
+ <format>when-not-present</format>
+ <description>Use the name the client sent. If the client sent no name, generate one for
+ the client</description>
+ </valueHelp>
+ <constraint>
+ <regex>(never|always|when-present|when-not-present)</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid replace client name mode</constraintErrorMessage>
+ </properties>
+</leafNode>
+<leafNode name="generated-prefix">
+ <properties>
+ <help>The prefix used in the generation of an FQDN</help>
+ <constraint>
+ <validator name="fqdn" />
+ </constraint>
+ <constraintErrorMessage>Invalid generated prefix</constraintErrorMessage>
+ </properties>
+</leafNode>
+<leafNode name="qualifying-suffix">
+ <properties>
+ <help>The suffix used when generating an FQDN, or when qualifying a partial name</help>
+ <constraint>
+ <validator name="fqdn" />
+ </constraint>
+ <constraintErrorMessage>Invalid qualifying suffix</constraintErrorMessage>
+ </properties>
+</leafNode>
+<leafNode name="update-on-renew">
+ <properties>
+ <help>Update DNS record on lease renew</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Update DNS record on lease renew</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Do not update DNS record on lease renew</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ <constraintErrorMessage>Set it to either enable or disable</constraintErrorMessage>
+ </properties>
+</leafNode>
+<leafNode name="conflict-resolution">
+ <properties>
+ <help>DNS conflict resolution behavior</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable DNS conflict resolution</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable DNS conflict resolution</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ <constraintErrorMessage>Set it to either enable or disable</constraintErrorMessage>
+ </properties>
+</leafNode>
+<leafNode name="ttl-percent">
+ <properties>
+ <help>Calculate TTL of the DNS record as a percentage of the lease lifetime</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-100" />
+ </constraint>
+ <constraintErrorMessage>Invalid qualifying suffix</constraintErrorMessage>
+ </properties>
+</leafNode>
+<leafNode name="hostname-char-set">
+ <properties>
+ <help>A regular expression describing the invalid character set in the host name</help>
+ </properties>
+</leafNode>
+<leafNode name="hostname-char-replacement">
+ <properties>
+ <help>A string of zero or more characters with which to replace each invalid character in
+ the host name</help>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/dhcp/option-v4.xml.i b/interface-definitions/include/dhcp/option-v4.xml.i
index bd6fc6043..08fbcca4a 100644
--- a/interface-definitions/include/dhcp/option-v4.xml.i
+++ b/interface-definitions/include/dhcp/option-v4.xml.i
@@ -59,6 +59,18 @@
<constraintErrorMessage>DHCP client prefix length must be 0 to 32</constraintErrorMessage>
</properties>
</leafNode>
+ <leafNode name="capwap-controller">
+ <properties>
+ <help>IP address of CAPWAP access controller (Option 138)</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>CAPWAP AC controller</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="default-router">
<properties>
<help>IP address of default router</help>
diff --git a/interface-definitions/include/dhcp/option-v6.xml.i b/interface-definitions/include/dhcp/option-v6.xml.i
index e1897f52d..202843ddf 100644
--- a/interface-definitions/include/dhcp/option-v6.xml.i
+++ b/interface-definitions/include/dhcp/option-v6.xml.i
@@ -7,6 +7,18 @@
#include <include/dhcp/captive-portal.xml.i>
#include <include/dhcp/domain-search.xml.i>
#include <include/name-server-ipv6.xml.i>
+ <leafNode name="capwap-controller">
+ <properties>
+ <help>IP address of CAPWAP access controller (Option 52)</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>CAPWAP AC controller</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="nis-domain">
<properties>
<help>NIS domain name for client to use</help>
diff --git a/interface-definitions/include/dhcp/ping-check.xml.i b/interface-definitions/include/dhcp/ping-check.xml.i
new file mode 100644
index 000000000..a506f68e4
--- /dev/null
+++ b/interface-definitions/include/dhcp/ping-check.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from dhcp/ping-check.xml.i -->
+<leafNode name="ping-check">
+ <properties>
+ <help>Sends ICMP Echo request to the address being assigned</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i
index 355b41fde..7393ff5c9 100644
--- a/interface-definitions/include/firewall/global-options.xml.i
+++ b/interface-definitions/include/firewall/global-options.xml.i
@@ -217,6 +217,14 @@
<help>Global firewall state-policy</help>
</properties>
<children>
+ <node name="offload">
+ <properties>
+ <help>All stateful forward traffic is offloaded to a flowtable</help>
+ </properties>
+ <children>
+ #include <include/firewall/offload-target.xml.i>
+ </children>
+ </node>
<node name="established">
<properties>
<help>Global firewall policy for packets part of an established connection</help>
diff --git a/interface-definitions/include/interface/ipv6-address-interface-identifier.xml.i b/interface-definitions/include/interface/ipv6-address-interface-identifier.xml.i
new file mode 100644
index 000000000..d173dfdb8
--- /dev/null
+++ b/interface-definitions/include/interface/ipv6-address-interface-identifier.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from interface/ipv6-address-interface-identifier.xml.i -->
+<leafNode name="interface-identifier">
+ <properties>
+ <help>SLAAC interface identifier</help>
+ <valueHelp>
+ <format>::h:h:h:h</format>
+ <description>Interface identifier</description>
+ </valueHelp>
+ <constraint>
+ <regex>::([0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){0,3})</regex>
+ </constraint>
+ <constraintErrorMessage>Interface identifier format must start with :: and may contain up four hextets (::h:h:h:h)</constraintErrorMessage>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/ipv6-address.xml.i b/interface-definitions/include/interface/ipv6-address.xml.i
deleted file mode 100644
index e1bdf02fd..000000000
--- a/interface-definitions/include/interface/ipv6-address.xml.i
+++ /dev/null
@@ -1,12 +0,0 @@
-<!-- include start from interface/ipv6-address.xml.i -->
-<node name="address">
- <properties>
- <help>IPv6 address configuration modes</help>
- </properties>
- <children>
- #include <include/interface/ipv6-address-autoconf.xml.i>
- #include <include/interface/ipv6-address-eui64.xml.i>
- #include <include/interface/ipv6-address-no-default-link-local.xml.i>
- </children>
-</node>
-<!-- include end -->
diff --git a/interface-definitions/include/interface/ipv6-options-with-nd.xml.i b/interface-definitions/include/interface/ipv6-options-with-nd.xml.i
new file mode 100644
index 000000000..5894104b3
--- /dev/null
+++ b/interface-definitions/include/interface/ipv6-options-with-nd.xml.i
@@ -0,0 +1,9 @@
+ <node name="ipv6">
+ <children>
+ <node name="address">
+ <children>
+ #include <include/interface/ipv6-address-interface-identifier.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
diff --git a/interface-definitions/include/interface/ipv6-options.xml.i b/interface-definitions/include/interface/ipv6-options.xml.i
index ec6ec64ee..f84a9f2cd 100644
--- a/interface-definitions/include/interface/ipv6-options.xml.i
+++ b/interface-definitions/include/interface/ipv6-options.xml.i
@@ -8,9 +8,18 @@
#include <include/interface/base-reachable-time.xml.i>
#include <include/interface/disable-forwarding.xml.i>
#include <include/interface/ipv6-accept-dad.xml.i>
- #include <include/interface/ipv6-address.xml.i>
#include <include/interface/ipv6-dup-addr-detect-transmits.xml.i>
#include <include/interface/source-validation.xml.i>
+ <node name="address">
+ <properties>
+ <help>IPv6 address configuration modes</help>
+ </properties>
+ <children>
+ #include <include/interface/ipv6-address-autoconf.xml.i>
+ #include <include/interface/ipv6-address-eui64.xml.i>
+ #include <include/interface/ipv6-address-no-default-link-local.xml.i>
+ </children>
+ </node>
</children>
</node>
<!-- include end -->
diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i
index 02e7ab057..65ca10207 100644
--- a/interface-definitions/include/interface/vif-s.xml.i
+++ b/interface-definitions/include/interface/vif-s.xml.i
@@ -21,6 +21,7 @@
#include <include/interface/vlan-protocol.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mirror.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
@@ -41,6 +42,7 @@
#include <include/interface/disable.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mirror.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i
index ec3921bf6..87f91c5ce 100644
--- a/interface-definitions/include/interface/vif.xml.i
+++ b/interface-definitions/include/interface/vif.xml.i
@@ -46,6 +46,7 @@
</leafNode>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mirror.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
diff --git a/interface-definitions/include/version/ids-version.xml.i b/interface-definitions/include/version/ids-version.xml.i
index 9133be02b..6d4e92c21 100644
--- a/interface-definitions/include/version/ids-version.xml.i
+++ b/interface-definitions/include/version/ids-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/ids-version.xml.i -->
-<syntaxVersion component='ids' version='1'></syntaxVersion>
+<syntaxVersion component='ids' version='2'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces_bonding.xml.in b/interface-definitions/interfaces_bonding.xml.in
index b17cad478..cdacae2d0 100644
--- a/interface-definitions/interfaces_bonding.xml.in
+++ b/interface-definitions/interfaces_bonding.xml.in
@@ -141,6 +141,7 @@
</leafNode>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/mac.xml.i>
<leafNode name="mii-mon-interval">
<properties>
diff --git a/interface-definitions/interfaces_bridge.xml.in b/interface-definitions/interfaces_bridge.xml.in
index 29dd61df5..667ae3b19 100644
--- a/interface-definitions/interfaces_bridge.xml.in
+++ b/interface-definitions/interfaces_bridge.xml.in
@@ -93,6 +93,7 @@
</node>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mirror.xml.i>
<leafNode name="enable-vlan">
diff --git a/interface-definitions/interfaces_ethernet.xml.in b/interface-definitions/interfaces_ethernet.xml.in
index b3559a626..819ceb2cb 100644
--- a/interface-definitions/interfaces_ethernet.xml.in
+++ b/interface-definitions/interfaces_ethernet.xml.in
@@ -74,6 +74,7 @@
#include <include/interface/hw-id.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
#include <include/interface/mirror.xml.i>
diff --git a/interface-definitions/interfaces_geneve.xml.in b/interface-definitions/interfaces_geneve.xml.in
index c1e6c33d5..b85bd3b9e 100644
--- a/interface-definitions/interfaces_geneve.xml.in
+++ b/interface-definitions/interfaces_geneve.xml.in
@@ -21,6 +21,7 @@
#include <include/interface/disable.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mtu-1200-16000.xml.i>
#include <include/port-number.xml.i>
diff --git a/interface-definitions/interfaces_l2tpv3.xml.in b/interface-definitions/interfaces_l2tpv3.xml.in
index 5f816c956..381e86bd0 100644
--- a/interface-definitions/interfaces_l2tpv3.xml.in
+++ b/interface-definitions/interfaces_l2tpv3.xml.in
@@ -55,6 +55,7 @@
</leafNode>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/source-address-ipv4-ipv6.xml.i>
#include <include/interface/mirror.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
diff --git a/interface-definitions/interfaces_macsec.xml.in b/interface-definitions/interfaces_macsec.xml.in
index d825f8262..5279a9495 100644
--- a/interface-definitions/interfaces_macsec.xml.in
+++ b/interface-definitions/interfaces_macsec.xml.in
@@ -21,6 +21,7 @@
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/mirror.xml.i>
<node name="security">
<properties>
diff --git a/interface-definitions/interfaces_openvpn.xml.in b/interface-definitions/interfaces_openvpn.xml.in
index 3c844107e..6510ed733 100644
--- a/interface-definitions/interfaces_openvpn.xml.in
+++ b/interface-definitions/interfaces_openvpn.xml.in
@@ -135,6 +135,7 @@
</node>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/mirror.xml.i>
<leafNode name="hash">
<properties>
diff --git a/interface-definitions/interfaces_pppoe.xml.in b/interface-definitions/interfaces_pppoe.xml.in
index f24bc41d8..66a774e21 100644
--- a/interface-definitions/interfaces_pppoe.xml.in
+++ b/interface-definitions/interfaces_pppoe.xml.in
@@ -88,6 +88,7 @@
</properties>
<children>
#include <include/interface/ipv6-address-autoconf.xml.i>
+ #include <include/interface/ipv6-address-interface-identifier.xml.i>
</children>
</node>
#include <include/interface/adjust-mss.xml.i>
diff --git a/interface-definitions/interfaces_pseudo-ethernet.xml.in b/interface-definitions/interfaces_pseudo-ethernet.xml.in
index 031af3563..f13144bed 100644
--- a/interface-definitions/interfaces_pseudo-ethernet.xml.in
+++ b/interface-definitions/interfaces_pseudo-ethernet.xml.in
@@ -25,6 +25,7 @@
#include <include/interface/vrf.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/source-interface-ethernet.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mirror.xml.i>
diff --git a/interface-definitions/interfaces_vxlan.xml.in b/interface-definitions/interfaces_vxlan.xml.in
index 937acb123..f4cd4fcd2 100644
--- a/interface-definitions/interfaces_vxlan.xml.in
+++ b/interface-definitions/interfaces_vxlan.xml.in
@@ -45,6 +45,7 @@
</leafNode>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mtu-1200-16000.xml.i>
#include <include/interface/mirror.xml.i>
diff --git a/interface-definitions/interfaces_wireless.xml.in b/interface-definitions/interfaces_wireless.xml.in
index 474953500..1b5356caa 100644
--- a/interface-definitions/interfaces_wireless.xml.in
+++ b/interface-definitions/interfaces_wireless.xml.in
@@ -626,6 +626,7 @@
</leafNode>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/hw-id.xml.i>
<leafNode name="isolate-stations">
<properties>
diff --git a/interface-definitions/interfaces_wwan.xml.in b/interface-definitions/interfaces_wwan.xml.in
index 1580c3bcb..552806d4e 100644
--- a/interface-definitions/interfaces_wwan.xml.in
+++ b/interface-definitions/interfaces_wwan.xml.in
@@ -38,6 +38,7 @@
</leafNode>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/ipv6-options-with-nd.xml.i>
#include <include/interface/dial-on-demand.xml.i>
#include <include/interface/redirect.xml.i>
#include <include/interface/vrf.xml.i>
diff --git a/interface-definitions/policy_route.xml.in b/interface-definitions/policy_route.xml.in
index 9cc22540b..48f728923 100644
--- a/interface-definitions/policy_route.xml.in
+++ b/interface-definitions/policy_route.xml.in
@@ -35,6 +35,7 @@
#include <include/firewall/address-ipv6.xml.i>
#include <include/firewall/source-destination-group-ipv6.xml.i>
#include <include/firewall/port.xml.i>
+ #include <include/firewall/geoip.xml.i>
</children>
</node>
<node name="source">
@@ -45,6 +46,7 @@
#include <include/firewall/address-ipv6.xml.i>
#include <include/firewall/source-destination-group-ipv6.xml.i>
#include <include/firewall/port.xml.i>
+ #include <include/firewall/geoip.xml.i>
</children>
</node>
#include <include/policy/route-common.xml.i>
@@ -90,6 +92,7 @@
#include <include/firewall/address.xml.i>
#include <include/firewall/source-destination-group.xml.i>
#include <include/firewall/port.xml.i>
+ #include <include/firewall/geoip.xml.i>
</children>
</node>
<node name="source">
@@ -100,6 +103,7 @@
#include <include/firewall/address.xml.i>
#include <include/firewall/source-destination-group.xml.i>
#include <include/firewall/port.xml.i>
+ #include <include/firewall/geoip.xml.i>
</children>
</node>
#include <include/policy/route-common.xml.i>
diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in
index 9a194de4f..78f1cea4e 100644
--- a/interface-definitions/service_dhcp-server.xml.in
+++ b/interface-definitions/service_dhcp-server.xml.in
@@ -10,12 +10,111 @@
</properties>
<children>
#include <include/generic-disable-node.xml.i>
- <leafNode name="dynamic-dns-update">
+ <node name="dynamic-dns-update">
<properties>
<help>Dynamically update Domain Name System (RFC4702)</help>
- <valueless/>
</properties>
- </leafNode>
+ <children>
+ #include <include/dhcp/ddns-settings.xml.i>
+ <tagNode name="tsig-key">
+ <properties>
+ <help>TSIG key definition for DNS updates</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ <constraintErrorMessage>Invalid TSIG key name. May only contain letters, numbers, hyphen and underscore</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="algorithm">
+ <properties>
+ <help>TSIG key algorithm</help>
+ <completionHelp>
+ <list>md5 sha1 sha224 sha256 sha384 sha512</list>
+ </completionHelp>
+ <valueHelp>
+ <format>md5</format>
+ <description>MD5 HMAC algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha1</format>
+ <description>SHA1 HMAC algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha224</format>
+ <description>SHA224 HMAC algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha256</format>
+ <description>SHA256 HMAC algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha384</format>
+ <description>SHA384 HMAC algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha512</format>
+ <description>SHA512 HMAC algorithm</description>
+ </valueHelp>
+ <constraint>
+ <regex>(md5|sha1|sha224|sha256|sha384|sha512)</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid TSIG key algorithm</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="secret">
+ <properties>
+ <help>TSIG key secret (base64-encoded)</help>
+ <constraint>
+ <validator name="base64"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <tagNode name="forward-domain">
+ <properties>
+ <help>Forward DNS domain name</help>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ <constraintErrorMessage>Invalid forward DNS domain name</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="key-name">
+ <properties>
+ <help>TSIG key name for forward DNS updates</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ <constraintErrorMessage>Invalid TSIG key name. May only contain letters, numbers, numbers, hyphen and underscore</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ #include <include/dhcp/ddns-dns-server.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="reverse-domain">
+ <properties>
+ <help>Reverse DNS domain name</help>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ <constraintErrorMessage>Invalid reverse DNS domain name</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="key-name">
+ <properties>
+ <help>TSIG key name for reverse DNS updates</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ <constraintErrorMessage>Invalid TSIG key name. May only contain letters, numbers, numbers, hyphen and underscore</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ #include <include/dhcp/ddns-dns-server.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
<node name="high-availability">
<properties>
<help>DHCP high availability configuration</help>
@@ -105,6 +204,14 @@
<constraintErrorMessage>Invalid shared network name. May only contain letters, numbers and .-_</constraintErrorMessage>
</properties>
<children>
+ <node name="dynamic-dns-update">
+ <properties>
+ <help>Dynamically update Domain Name System (RFC4702)</help>
+ </properties>
+ <children>
+ #include <include/dhcp/ddns-settings.xml.i>
+ </children>
+ </node>
<leafNode name="authoritative">
<properties>
<help>Option to make DHCP server authoritative for this physical network</help>
@@ -112,6 +219,7 @@
</properties>
</leafNode>
#include <include/dhcp/option-v4.xml.i>
+ #include <include/dhcp/ping-check.xml.i>
#include <include/generic-description.xml.i>
#include <include/generic-disable-node.xml.i>
<tagNode name="subnet">
@@ -128,8 +236,17 @@
</properties>
<children>
#include <include/dhcp/option-v4.xml.i>
+ #include <include/dhcp/ping-check.xml.i>
#include <include/generic-description.xml.i>
#include <include/generic-disable-node.xml.i>
+ <node name="dynamic-dns-update">
+ <properties>
+ <help>Dynamically update Domain Name System (RFC4702)</help>
+ </properties>
+ <children>
+ #include <include/dhcp/ddns-settings.xml.i>
+ </children>
+ </node>
<leafNode name="exclude">
<properties>
<help>IP address to exclude from DHCP lease range</help>
diff --git a/interface-definitions/service_ids_ddos-protection.xml.in b/interface-definitions/service_ids_ddos-protection.xml.in
deleted file mode 100644
index 3ef2640b3..000000000
--- a/interface-definitions/service_ids_ddos-protection.xml.in
+++ /dev/null
@@ -1,167 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="service">
- <children>
- <node name="ids">
- <properties>
- <help>Intrusion Detection System</help>
- </properties>
- <children>
- <node name="ddos-protection" owner="${vyos_conf_scripts_dir}/service_ids_ddos-protection.py">
- <properties>
- <help>FastNetMon detection and protection parameters</help>
- <priority>731</priority>
- </properties>
- <children>
- <leafNode name="alert-script">
- <properties>
- <help>Path to fastnetmon alert script</help>
- </properties>
- </leafNode>
- <leafNode name="ban-time">
- <properties>
- <help>How long we should keep an IP in blocked state</help>
- <valueHelp>
- <format>u32:1-4294967294</format>
- <description>Time in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-4294967294"/>
- </constraint>
- </properties>
- <defaultValue>1900</defaultValue>
- </leafNode>
- <leafNode name="direction">
- <properties>
- <help>Direction for processing traffic</help>
- <completionHelp>
- <list>in out</list>
- </completionHelp>
- <constraint>
- <regex>(in|out)</regex>
- </constraint>
- <multi/>
- </properties>
- </leafNode>
- <leafNode name="excluded-network">
- <properties>
- <help>Specify IPv4 and IPv6 networks which are going to be excluded from protection</help>
- <valueHelp>
- <format>ipv4net</format>
- <description>IPv4 prefix(es) to exclude</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6net</format>
- <description>IPv6 prefix(es) to exclude</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-prefix"/>
- <validator name="ipv6-prefix"/>
- </constraint>
- <multi/>
- </properties>
- </leafNode>
- <leafNode name="listen-interface">
- <properties>
- <help>Listen interface for mirroring traffic</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- <multi/>
- </properties>
- </leafNode>
- <leafNode name="mode">
- <properties>
- <help>Traffic capture mode</help>
- <completionHelp>
- <list>mirror sflow</list>
- </completionHelp>
- <valueHelp>
- <format>mirror</format>
- <description>Listen to mirrored traffic</description>
- </valueHelp>
- <valueHelp>
- <format>sflow</format>
- <description>Capture sFlow flows</description>
- </valueHelp>
- <constraint>
- <regex>(mirror|sflow)</regex>
- </constraint>
- </properties>
- </leafNode>
- <node name="sflow">
- <properties>
- <help>Sflow settings</help>
- </properties>
- <children>
- #include <include/listen-address-ipv4-single.xml.i>
- #include <include/port-number.xml.i>
- <leafNode name="port">
- <defaultValue>6343</defaultValue>
- </leafNode>
- </children>
- </node>
- <leafNode name="network">
- <properties>
- <help>Specify IPv4 and IPv6 networks which belong to you</help>
- <valueHelp>
- <format>ipv4net</format>
- <description>Your IPv4 prefix(es)</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6net</format>
- <description>Your IPv6 prefix(es)</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-prefix"/>
- <validator name="ipv6-prefix"/>
- </constraint>
- <multi/>
- </properties>
- </leafNode>
- <node name="threshold">
- <properties>
- <help>Attack limits thresholds</help>
- </properties>
- <children>
- <node name="general">
- <properties>
- <help>General threshold</help>
- </properties>
- <children>
- #include <include/ids/threshold.xml.i>
- </children>
- </node>
- <node name="tcp">
- <properties>
- <help>TCP threshold</help>
- </properties>
- <children>
- #include <include/ids/threshold.xml.i>
- </children>
- </node>
- <node name="udp">
- <properties>
- <help>UDP threshold</help>
- </properties>
- <children>
- #include <include/ids/threshold.xml.i>
- </children>
- </node>
- <node name="icmp">
- <properties>
- <help>ICMP threshold</help>
- </properties>
- <children>
- #include <include/ids/threshold.xml.i>
- </children>
- </node>
- </children>
- </node>
- </children>
- </node>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in
index 3fd33540a..7f96cdb19 100644
--- a/interface-definitions/service_router-advert.xml.in
+++ b/interface-definitions/service_router-advert.xml.in
@@ -255,6 +255,19 @@
</leafNode>
</children>
</tagNode>
+ <leafNode name="auto-ignore">
+ <properties>
+ <help>IPv6 prefix to be excluded in Router Advertisements (RAs) - use in conjunction with the ::/64 wildcard prefix</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 prefix to be excluded</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
<tagNode name="prefix">
<properties>
<help>IPv6 prefix to be advertised in Router Advertisements (RAs)</help>
diff --git a/interface-definitions/system_option.xml.in b/interface-definitions/system_option.xml.in
index 638ac1a3d..c9240064f 100644
--- a/interface-definitions/system_option.xml.in
+++ b/interface-definitions/system_option.xml.in
@@ -69,6 +69,12 @@
</valueHelp>
</properties>
</leafNode>
+ <leafNode name="quiet">
+ <properties>
+ <help>Disable most log messages</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<node name="debug">
<properties>
<help>Dynamic debugging for kernel module</help>
diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in
index 0cf526fad..873a4f882 100644
--- a/interface-definitions/vpn_ipsec.xml.in
+++ b/interface-definitions/vpn_ipsec.xml.in
@@ -1244,6 +1244,63 @@
<children>
#include <include/ipsec/bind.xml.i>
#include <include/ipsec/esp-group.xml.i>
+ <node name="traffic-selector">
+ <properties>
+ <help>Traffic-selectors parameters</help>
+ </properties>
+ <children>
+ <node name="local">
+ <properties>
+ <help>Local parameters for interesting traffic</help>
+ </properties>
+ <children>
+ <leafNode name="prefix">
+ <properties>
+ <help>Local IPv4 or IPv6 prefix</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>Local IPv4 prefix</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>Local IPv6 prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="remote">
+ <properties>
+ <help>Remote parameters for interesting traffic</help>
+ </properties>
+ <children>
+ <leafNode name="prefix">
+ <properties>
+ <help>Remote IPv4 or IPv6 prefix</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>Remote IPv4 prefix</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>Remote IPv6 prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/libvyosconfig b/libvyosconfig
-Subproject 58dbb42e827e3d326c6e0e9470334d4d5c7c396
+Subproject 1dedc69476d707718031c45b53b626da8badf86
diff --git a/op-mode-definitions/firewall.xml.in b/op-mode-definitions/firewall.xml.in
index 82e6c8668..21159eb1b 100755
--- a/op-mode-definitions/firewall.xml.in
+++ b/op-mode-definitions/firewall.xml.in
@@ -14,9 +14,16 @@
<path>firewall group address-group</path>
<path>firewall group network-group</path>
<path>firewall group port-group</path>
+ <path>firewall group domain-group</path>
+ <path>firewall group dynamic-group address-group</path>
+ <path>firewall group dynamic-group ipv6-address-group</path>
<path>firewall group interface-group</path>
<path>firewall group ipv6-address-group</path>
<path>firewall group ipv6-network-group</path>
+ <path>firewall group mac-group</path>
+ <path>firewall group network-group</path>
+ <path>firewall group port-group</path>
+ <path>firewall group remote-group</path>
</completionHelp>
</properties>
<children>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index b9ef8f48e..91e1c93ef 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -17,19 +17,6 @@
</properties>
<command>SYSTEMD_COLORS=false grc journalctl --no-hostname --follow --boot</command>
</node>
- <node name="ids">
- <properties>
- <help>Monitor Intrusion Detection System log</help>
- </properties>
- <children>
- <leafNode name="ddos-protection">
- <properties>
- <help>Monitor last lines of DDOS protection</help>
- </properties>
- <command>journalctl --no-hostname --follow --boot --unit fastnetmon.service</command>
- </leafNode>
- </children>
- </node>
<leafNode name="certbot">
<properties>
<help>Monitor last lines of certbot log</help>
diff --git a/op-mode-definitions/clear-session.xml.in b/op-mode-definitions/reset-session.xml.in
index bfafe6312..1e52e278b 100644
--- a/op-mode-definitions/clear-session.xml.in
+++ b/op-mode-definitions/reset-session.xml.in
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<interfaceDefinition>
- <node name="clear">
+ <node name="reset">
<children>
<tagNode name="session">
<properties>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index 5ee7c973f..ee2e2bf70 100755
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -50,6 +50,39 @@
</properties>
<command>cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e heartbeat -e cl_status -e mach_down -e ha_log</command>
</leafNode>
+ <node name="conntrack">
+ <properties>
+ <help>Show log for conntrack events</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -t vyos-conntrack-logger --grep='\[(NEW|UPDATE|DESTROY)\]'</command>
+ <children>
+ <node name="event">
+ <properties>
+ <help>Show log for conntrack events</help>
+ </properties>
+ <children>
+ <leafNode name="new">
+ <properties>
+ <help>Show log for conntrack events</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -t vyos-conntrack-logger --grep='\[(NEW)\]'</command>
+ </leafNode>
+ <leafNode name="update">
+ <properties>
+ <help>Show log for conntrack events</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -t vyos-conntrack-logger --grep='\[(UPDATE)\]'</command>
+ </leafNode>
+ <leafNode name="destroy">
+ <properties>
+ <help>Show log for Conntrack Events</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -t vyos-conntrack-logger --grep='\[(DESTROY)\]'</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
<leafNode name="conntrack-sync">
<properties>
<help>Show log for Conntrack-sync</help>
@@ -62,19 +95,6 @@
</properties>
<command>journalctl --no-hostname --boot --unit conserver-server.service</command>
</leafNode>
- <node name="ids">
- <properties>
- <help>Show log for for Intrusion Detection System</help>
- </properties>
- <children>
- <leafNode name="ddos-protection">
- <properties>
- <help>Show log for DDOS protection</help>
- </properties>
- <command>journalctl --no-hostname --boot --unit fastnetmon.service</command>
- </leafNode>
- </children>
- </node>
<node name="dhcp">
<properties>
<help>Show log for Dynamic Host Control Protocol (DHCP)</help>
@@ -139,7 +159,7 @@
<properties>
<help>Show log for Firewall</help>
</properties>
- <command>journalctl --no-hostname --boot -k | egrep "(ipv[46]|bri)-(FWD|INP|OUT|NAM)"</command>
+ <command>journalctl --no-hostname --boot -k --grep='(ipv[46]|bri)-(FWD|INP|OUT|NAM)|STATE-POLICY'</command>
<children>
<node name="bridge">
<properties>
diff --git a/op-mode-definitions/system-image.xml.in b/op-mode-definitions/system-image.xml.in
index 44b055be6..847029dcd 100644
--- a/op-mode-definitions/system-image.xml.in
+++ b/op-mode-definitions/system-image.xml.in
@@ -193,7 +193,7 @@
<properties>
<help>Show installed VyOS images</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/image_info.py show_images_summary</command>
+ <command>${vyos_op_scripts_dir}/image_info.py show_images_summary</command>
<children>
<node name="details">
<properties>
diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py
index 94215531d..81d986658 100644
--- a/python/vyos/component_version.py
+++ b/python/vyos/component_version.py
@@ -49,7 +49,9 @@ DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot')
REGEX_WARN_VYOS = r'(// Warning: Do not remove the following line.)'
REGEX_WARN_VYATTA = r'(/\* Warning: Do not remove the following line. \*/)'
REGEX_COMPONENT_VERSION_VYOS = r'// vyos-config-version:\s+"([\w@:-]+)"\s*'
-REGEX_COMPONENT_VERSION_VYATTA = r'/\* === vyatta-config-version:\s+"([\w@:-]+)"\s+=== \*/'
+REGEX_COMPONENT_VERSION_VYATTA = (
+ r'/\* === vyatta-config-version:\s+"([\w@:-]+)"\s+=== \*/'
+)
REGEX_RELEASE_VERSION_VYOS = r'// Release version:\s+(\S*)\s*'
REGEX_RELEASE_VERSION_VYATTA = r'/\* Release version:\s+(\S*)\s*\*/'
@@ -62,16 +64,31 @@ CONFIG_FILE_VERSION = """\
warn_filter_vyos = re.compile(REGEX_WARN_VYOS)
warn_filter_vyatta = re.compile(REGEX_WARN_VYATTA)
-regex_filter = { 'vyos': dict(zip(['component', 'release'],
- [re.compile(REGEX_COMPONENT_VERSION_VYOS),
- re.compile(REGEX_RELEASE_VERSION_VYOS)])),
- 'vyatta': dict(zip(['component', 'release'],
- [re.compile(REGEX_COMPONENT_VERSION_VYATTA),
- re.compile(REGEX_RELEASE_VERSION_VYATTA)])) }
+regex_filter = {
+ 'vyos': dict(
+ zip(
+ ['component', 'release'],
+ [
+ re.compile(REGEX_COMPONENT_VERSION_VYOS),
+ re.compile(REGEX_RELEASE_VERSION_VYOS),
+ ],
+ )
+ ),
+ 'vyatta': dict(
+ zip(
+ ['component', 'release'],
+ [
+ re.compile(REGEX_COMPONENT_VERSION_VYATTA),
+ re.compile(REGEX_RELEASE_VERSION_VYATTA),
+ ],
+ )
+ ),
+}
+
@dataclass
class VersionInfo:
- component: Optional[dict[str,int]] = None
+ component: Optional[dict[str, int]] = None
release: str = get_version()
vintage: str = 'vyos'
config_body: Optional[str] = None
@@ -84,8 +101,9 @@ class VersionInfo:
return bool(self.config_body is None)
def update_footer(self):
- f = CONFIG_FILE_VERSION.format(component_to_string(self.component),
- self.release)
+ f = CONFIG_FILE_VERSION.format(
+ component_to_string(self.component), self.release
+ )
self.footer_lines = f.splitlines()
def update_syntax(self):
@@ -121,13 +139,16 @@ class VersionInfo:
except Exception as e:
raise ValueError(e) from e
+
def component_to_string(component: dict) -> str:
- l = [f'{k}@{v}' for k, v in sorted(component.items(), key=lambda x: x[0])]
+ l = [f'{k}@{v}' for k, v in sorted(component.items(), key=lambda x: x[0])] # noqa: E741
return ':'.join(l)
+
def component_from_string(string: str) -> dict:
return {k: int(v) for k, v in re.findall(r'([\w,-]+)@(\d+)', string)}
+
def version_info_from_file(config_file) -> VersionInfo:
"""Return config file component and release version info."""
version_info = VersionInfo()
@@ -166,27 +187,27 @@ def version_info_from_file(config_file) -> VersionInfo:
return version_info
+
def version_info_from_system() -> VersionInfo:
"""Return system component and release version info."""
d = component_version()
sort_d = dict(sorted(d.items(), key=lambda x: x[0]))
- version_info = VersionInfo(
- component = sort_d,
- release = get_version(),
- vintage = 'vyos'
- )
+ version_info = VersionInfo(component=sort_d, release=get_version(), vintage='vyos')
return version_info
+
def version_info_copy(v: VersionInfo) -> VersionInfo:
"""Make a copy of dataclass."""
return replace(v)
+
def version_info_prune_component(x: VersionInfo, y: VersionInfo) -> VersionInfo:
"""In place pruning of component keys of x not in y."""
if x.component is None or y.component is None:
return
- x.component = { k: v for k,v in x.component.items() if k in y.component }
+ x.component = {k: v for k, v in x.component.items() if k in y.component}
+
def add_system_version(config_str: str = None, out_file: str = None):
"""Wrap config string with system version and write to out_file.
@@ -202,3 +223,11 @@ def add_system_version(config_str: str = None, out_file: str = None):
version_info.write(out_file)
else:
sys.stdout.write(version_info.write_string())
+
+
+def append_system_version(file: str):
+ """Append system version data to existing file"""
+ version_info = version_info_from_system()
+ version_info.update_footer()
+ with open(file, 'a') as f:
+ f.write(version_info.write_string())
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 78b98a3eb..ff0a15933 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -517,6 +517,14 @@ def get_interface_dict(config, base, ifname='', recursive_defaults=True, with_pk
else:
dict['ipv6']['address'].update({'eui64_old': eui64})
+ interface_identifier = leaf_node_changed(config, base + [ifname, 'ipv6', 'address', 'interface-identifier'])
+ if interface_identifier:
+ tmp = dict_search('ipv6.address', dict)
+ if not tmp:
+ dict.update({'ipv6': {'address': {'interface_identifier_old': interface_identifier}}})
+ else:
+ dict['ipv6']['address'].update({'interface_identifier_old': interface_identifier})
+
for vif, vif_config in dict.get('vif', {}).items():
# Add subinterface name to dictionary
dict['vif'][vif].update({'ifname' : f'{ifname}.{vif}'})
@@ -626,6 +634,23 @@ def get_vlan_ids(interface):
return vlan_ids
+def get_vlans_ids_and_range(interface):
+ vlan_ids = set()
+
+ vlan_filter_status = json.loads(cmd(f'bridge -j -d vlan show dev {interface}'))
+
+ if vlan_filter_status is not None:
+ for interface_status in vlan_filter_status:
+ for vlan_entry in interface_status.get("vlans", []):
+ start = vlan_entry["vlan"]
+ end = vlan_entry.get("vlanEnd")
+ if end:
+ vlan_ids.add(f"{start}-{end}")
+ else:
+ vlan_ids.add(str(start))
+
+ return vlan_ids
+
def get_accel_dict(config, base, chap_secrets, with_pki=False):
"""
Common utility function to retrieve and mangle the Accel-PPP configuration
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index 90b96b88c..a3be29881 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -21,6 +21,10 @@ import subprocess
from vyos.defaults import directories
from vyos.utils.process import is_systemd_service_running
from vyos.utils.dict import dict_to_paths
+from vyos.utils.boot import boot_configuration_complete
+from vyos.vyconf_session import VyconfSession
+
+vyconf_backend = False
CLI_SHELL_API = '/bin/cli-shell-api'
SET = '/opt/vyatta/sbin/my_set'
@@ -165,6 +169,11 @@ class ConfigSession(object):
self.__run_command([CLI_SHELL_API, 'setupSession'])
+ if vyconf_backend and boot_configuration_complete():
+ self._vyconf_session = VyconfSession(on_error=ConfigSessionError)
+ else:
+ self._vyconf_session = None
+
def __del__(self):
try:
output = (
@@ -209,7 +218,10 @@ class ConfigSession(object):
value = []
else:
value = [value]
- self.__run_command([SET] + path + value)
+ if self._vyconf_session is None:
+ self.__run_command([SET] + path + value)
+ else:
+ self._vyconf_session.set(path + value)
def set_section(self, path: list, d: dict):
try:
@@ -223,7 +235,10 @@ class ConfigSession(object):
value = []
else:
value = [value]
- self.__run_command([DELETE] + path + value)
+ if self._vyconf_session is None:
+ self.__run_command([DELETE] + path + value)
+ else:
+ self._vyconf_session.delete(path + value)
def load_section(self, path: list, d: dict):
try:
@@ -261,20 +276,34 @@ class ConfigSession(object):
self.__run_command([COMMENT] + path + value)
def commit(self):
- out = self.__run_command([COMMIT])
+ if self._vyconf_session is None:
+ out = self.__run_command([COMMIT])
+ else:
+ out, _ = self._vyconf_session.commit()
+
return out
def discard(self):
- self.__run_command([DISCARD])
+ if self._vyconf_session is None:
+ self.__run_command([DISCARD])
+ else:
+ out, _ = self._vyconf_session.discard()
def show_config(self, path, format='raw'):
- config_data = self.__run_command(SHOW_CONFIG + path)
+ if self._vyconf_session is None:
+ config_data = self.__run_command(SHOW_CONFIG + path)
+ else:
+ config_data, _ = self._vyconf_session.show_config()
if format == 'raw':
return config_data
def load_config(self, file_path):
- out = self.__run_command(LOAD_CONFIG + [file_path])
+ if self._vyconf_session is None:
+ out = self.__run_command(LOAD_CONFIG + [file_path])
+ else:
+ out, _ = self._vyconf_session.load_config(file=file_path)
+
return out
def load_explicit(self, file_path):
@@ -287,11 +316,21 @@ class ConfigSession(object):
raise ConfigSessionError(e) from e
def migrate_and_load_config(self, file_path):
- out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path])
+ if self._vyconf_session is None:
+ out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path])
+ else:
+ out, _ = self._vyconf_session.load_config(file=file_path, migrate=True)
+
return out
def save_config(self, file_path):
- out = self.__run_command(SAVE_CONFIG + [file_path])
+ if self._vyconf_session is None:
+ out = self.__run_command(SAVE_CONFIG + [file_path])
+ else:
+ out, _ = self._vyconf_session.save_config(
+ file=file_path, append_version=True
+ )
+
return out
def install_image(self, url):
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 4084425b1..d5f443f15 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -92,6 +92,9 @@ def verify_mtu_ipv6(config):
tmp = dict_search('ipv6.address.eui64', config)
if tmp != None: raise ConfigError(error_msg)
+ tmp = dict_search('ipv6.address.interface_identifier', config)
+ if tmp != None: raise ConfigError(error_msg)
+
def verify_vrf(config):
"""
Common helper function used by interface implementations to perform
@@ -356,6 +359,7 @@ def verify_vlan_config(config):
verify_vrf(vlan)
verify_mirror_redirect(vlan)
verify_mtu_parent(vlan, config)
+ verify_mtu_ipv6(vlan)
# 802.1ad (Q-in-Q) VLANs
for s_vlan_id in config.get('vif_s', {}):
@@ -367,6 +371,7 @@ def verify_vlan_config(config):
verify_vrf(s_vlan)
verify_mirror_redirect(s_vlan)
verify_mtu_parent(s_vlan, config)
+ verify_mtu_ipv6(s_vlan)
for c_vlan_id in s_vlan.get('vif_c', {}):
c_vlan = s_vlan['vif_c'][c_vlan_id]
@@ -378,6 +383,7 @@ def verify_vlan_config(config):
verify_mirror_redirect(c_vlan)
verify_mtu_parent(c_vlan, config)
verify_mtu_parent(c_vlan, s_vlan)
+ verify_mtu_ipv6(c_vlan)
def verify_diffie_hellman_length(file, min_keysize):
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 2b08ff68e..7efccded6 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -43,7 +43,7 @@ directories = {
}
systemd_services = {
- 'rsyslog' : 'rsyslog.service',
+ 'syslog' : 'syslog.service',
'snmpd' : 'snmpd.service',
}
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 9f01f8be1..9c320c82d 100755
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -233,6 +233,9 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
hook_name = 'prerouting'
if hook == 'NAM':
hook_name = f'name'
+ # for policy
+ if hook == 'route' or hook == 'route6':
+ hook_name = hook
output.append(f'{ip_name} {prefix}addr {operator} @GEOIP_CC{def_suffix}_{hook_name}_{fw_name}_{rule_id}')
if 'mac_address' in side_conf:
@@ -738,14 +741,14 @@ class GeoIPLock(object):
def __exit__(self, exc_type, exc_value, tb):
os.unlink(self.file)
-def geoip_update(firewall, force=False):
+def geoip_update(firewall=None, policy=None, force=False):
with GeoIPLock(geoip_lock_file) as lock:
if not lock:
print("Script is already running")
return False
- if not firewall:
- print("Firewall is not configured")
+ if not firewall and not policy:
+ print("Firewall and policy are not configured")
return True
if not os.path.exists(geoip_database):
@@ -760,23 +763,41 @@ def geoip_update(firewall, force=False):
ipv4_sets = {}
ipv6_sets = {}
+ ipv4_codes_policy = {}
+ ipv6_codes_policy = {}
+
+ ipv4_sets_policy = {}
+ ipv6_sets_policy = {}
+
# Map country codes to set names
- for codes, path in dict_search_recursive(firewall, 'country_code'):
- set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
- if ( path[0] == 'ipv4'):
- for code in codes:
- ipv4_codes.setdefault(code, []).append(set_name)
- elif ( path[0] == 'ipv6' ):
- set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}'
- for code in codes:
- ipv6_codes.setdefault(code, []).append(set_name)
-
- if not ipv4_codes and not ipv6_codes:
+ if firewall:
+ for codes, path in dict_search_recursive(firewall, 'country_code'):
+ set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
+ if ( path[0] == 'ipv4'):
+ for code in codes:
+ ipv4_codes.setdefault(code, []).append(set_name)
+ elif ( path[0] == 'ipv6' ):
+ set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}'
+ for code in codes:
+ ipv6_codes.setdefault(code, []).append(set_name)
+
+ if policy:
+ for codes, path in dict_search_recursive(policy, 'country_code'):
+ set_name = f'GEOIP_CC_{path[0]}_{path[1]}_{path[3]}'
+ if ( path[0] == 'route'):
+ for code in codes:
+ ipv4_codes_policy.setdefault(code, []).append(set_name)
+ elif ( path[0] == 'route6' ):
+ set_name = f'GEOIP_CC6_{path[0]}_{path[1]}_{path[3]}'
+ for code in codes:
+ ipv6_codes_policy.setdefault(code, []).append(set_name)
+
+ if not ipv4_codes and not ipv6_codes and not ipv4_codes_policy and not ipv6_codes_policy:
if force:
- print("GeoIP not in use by firewall")
+ print("GeoIP not in use by firewall and policy")
return True
- geoip_data = geoip_load_data([*ipv4_codes, *ipv6_codes])
+ geoip_data = geoip_load_data([*ipv4_codes, *ipv6_codes, *ipv4_codes_policy, *ipv6_codes_policy])
# Iterate IP blocks to assign to sets
for start, end, code in geoip_data:
@@ -785,19 +806,29 @@ def geoip_update(firewall, force=False):
ip_range = f'{start}-{end}' if start != end else start
for setname in ipv4_codes[code]:
ipv4_sets.setdefault(setname, []).append(ip_range)
+ if code in ipv4_codes_policy and ipv4:
+ ip_range = f'{start}-{end}' if start != end else start
+ for setname in ipv4_codes_policy[code]:
+ ipv4_sets_policy.setdefault(setname, []).append(ip_range)
if code in ipv6_codes and not ipv4:
ip_range = f'{start}-{end}' if start != end else start
for setname in ipv6_codes[code]:
ipv6_sets.setdefault(setname, []).append(ip_range)
+ if code in ipv6_codes_policy and not ipv4:
+ ip_range = f'{start}-{end}' if start != end else start
+ for setname in ipv6_codes_policy[code]:
+ ipv6_sets_policy.setdefault(setname, []).append(ip_range)
render(nftables_geoip_conf, 'firewall/nftables-geoip-update.j2', {
'ipv4_sets': ipv4_sets,
- 'ipv6_sets': ipv6_sets
+ 'ipv6_sets': ipv6_sets,
+ 'ipv4_sets_policy': ipv4_sets_policy,
+ 'ipv6_sets_policy': ipv6_sets_policy,
})
result = run(f'nft --file {nftables_geoip_conf}')
if result != 0:
- print('Error: GeoIP failed to update firewall')
+ print('Error: GeoIP failed to update firewall/policy')
return False
return True
diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py
index 8d469e3e2..524167d8b 100644
--- a/python/vyos/frrender.py
+++ b/python/vyos/frrender.py
@@ -92,7 +92,7 @@ def get_frrender_dict(conf, argv=None) -> dict:
if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None:
del default_values['area'][area_num]['area_type']['nssa']
- for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']:
+ for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'nhrp', 'rip', 'static']:
if dict_search(f'redistribute.{protocol}', ospf) is None:
del default_values['redistribute'][protocol]
if not bool(default_values['redistribute']):
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
index d534dade7..f81026965 100644
--- a/python/vyos/ifconfig/bridge.py
+++ b/python/vyos/ifconfig/bridge.py
@@ -19,7 +19,7 @@ from vyos.utils.assertion import assert_list
from vyos.utils.assertion import assert_positive
from vyos.utils.dict import dict_search
from vyos.utils.network import interface_exists
-from vyos.configdict import get_vlan_ids
+from vyos.configdict import get_vlans_ids_and_range
from vyos.configdict import list_diff
@Interface.register
@@ -380,7 +380,7 @@ class BridgeIf(Interface):
add_vlan = []
native_vlan_id = None
allowed_vlan_ids= []
- cur_vlan_ids = get_vlan_ids(interface)
+ cur_vlan_ids = get_vlans_ids_and_range(interface)
if 'native_vlan' in interface_config:
vlan_id = interface_config['native_vlan']
@@ -389,14 +389,8 @@ class BridgeIf(Interface):
if 'allowed_vlan' in interface_config:
for vlan in interface_config['allowed_vlan']:
- vlan_range = vlan.split('-')
- if len(vlan_range) == 2:
- for vlan_add in range(int(vlan_range[0]),int(vlan_range[1]) + 1):
- add_vlan.append(str(vlan_add))
- allowed_vlan_ids.append(str(vlan_add))
- else:
- add_vlan.append(vlan)
- allowed_vlan_ids.append(vlan)
+ add_vlan.append(vlan)
+ allowed_vlan_ids.append(vlan)
# Remove redundant VLANs from the system
for vlan in list_diff(cur_vlan_ids, add_vlan):
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 979b62578..003a273c0 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -22,6 +22,7 @@ from copy import deepcopy
from glob import glob
from ipaddress import IPv4Network
+from ipaddress import IPv6Interface
from netifaces import ifaddresses
# this is not the same as socket.AF_INET/INET6
from netifaces import AF_INET
@@ -909,7 +910,11 @@ class Interface(Control):
tmp = self.get_interface('ipv6_autoconf')
if tmp == autoconf:
return None
- return self.set_interface('ipv6_autoconf', autoconf)
+ rc = self.set_interface('ipv6_autoconf', autoconf)
+ if autoconf == '0':
+ flushed = self.flush_ipv6_slaac_addrs()
+ self.flush_ipv6_slaac_routes(ra_addrs=flushed)
+ return rc
def add_ipv6_eui64_address(self, prefix):
"""
@@ -937,6 +942,20 @@ class Interface(Control):
prefixlen = prefix.split('/')[1]
self.del_addr(f'{eui64}/{prefixlen}')
+ def set_ipv6_interface_identifier(self, identifier):
+ """
+ Set the interface identifier for IPv6 autoconf.
+ """
+ cmd = f'ip token set {identifier} dev {self.ifname}'
+ self._cmd(cmd)
+
+ def del_ipv6_interface_identifier(self):
+ """
+ Delete the interface identifier for IPv6 autoconf.
+ """
+ cmd = f'ip token delete dev {self.ifname}'
+ self._cmd(cmd)
+
def set_ipv6_forwarding(self, forwarding):
"""
Configure IPv6 interface-specific Host/Router behaviour.
@@ -1310,6 +1329,71 @@ class Interface(Control):
# flush all addresses
self._cmd(cmd)
+ def flush_ipv6_slaac_addrs(self) -> list:
+ """
+ Flush all IPv6 addresses installed in response to router advertisement
+ messages from this interface.
+
+ Will raise an exception on error.
+ Will return a list of flushed IPv6 addresses.
+ """
+ netns = get_interface_namespace(self.ifname)
+ netns_cmd = f'ip netns exec {netns}' if netns else ''
+ tmp = get_interface_address(self.ifname)
+ if not tmp or 'addr_info' not in tmp:
+ return
+
+ # Parse interface IP addresses. Example data:
+ # {'family': 'inet6', 'local': '2001:db8:1111:0:250:56ff:feb3:38c5',
+ # 'prefixlen': 64, 'scope': 'global', 'dynamic': True,
+ # 'mngtmpaddr': True, 'protocol': 'kernel_ra',
+ # 'valid_life_time': 2591987, 'preferred_life_time': 14387}
+ flushed = []
+ for addr_info in tmp['addr_info']:
+ if 'protocol' not in addr_info:
+ continue
+ if (addr_info['protocol'] == 'kernel_ra' and
+ addr_info['scope'] == 'global'):
+ # Flush IPv6 addresses installed by router advertisement
+ ra_addr = f"{addr_info['local']}/{addr_info['prefixlen']}"
+ flushed.append(ra_addr)
+ cmd = f'{netns_cmd} ip -6 addr del dev {self.ifname} {ra_addr}'
+ self._cmd(cmd)
+ return flushed
+
+ def flush_ipv6_slaac_routes(self, ra_addrs: list=[]) -> None:
+ """
+ Flush IPv6 default routes installed in response to router advertisement
+ messages from this interface.
+
+ Will raise an exception on error.
+ """
+ # Find IPv6 connected prefixes for flushed SLAAC addresses
+ connected = []
+ for addr in ra_addrs if isinstance(ra_addrs, list) else []:
+ connected.append(str(IPv6Interface(addr).network))
+
+ netns = get_interface_namespace(self.ifname)
+ netns_cmd = f'ip netns exec {netns}' if netns else ''
+
+ tmp = self._cmd(f'{netns_cmd} ip -j -6 route show dev {self.ifname}')
+ tmp = json.loads(tmp)
+ # Parse interface routes. Example data:
+ # {'dst': 'default', 'gateway': 'fe80::250:56ff:feb3:cdba',
+ # 'protocol': 'ra', 'metric': 1024, 'flags': [], 'expires': 1398,
+ # 'metrics': [{'hoplimit': 64}], 'pref': 'medium'}
+ for route in tmp:
+ # If it's a default route received from RA, delete it
+ if (dict_search('dst', route) == 'default' and
+ dict_search('protocol', route) == 'ra'):
+ self._cmd(f'{netns_cmd} ip -6 route del default via {route["gateway"]} dev {self.ifname}')
+ # Remove connected prefixes received from RA
+ if dict_search('dst', route) in connected:
+ # If it's a connected prefix, delete it
+ self._cmd(f'{netns_cmd} ip -6 route del {route["dst"]} dev {self.ifname}')
+
+ return None
+
def add_to_bridge(self, bridge_dict):
"""
Adds the interface to the bridge with the passed port config.
@@ -1320,8 +1404,6 @@ class Interface(Control):
# drop all interface addresses first
self.flush_addrs()
- ifname = self.ifname
-
for bridge, bridge_config in bridge_dict.items():
# add interface to bridge - use Section.klass to get BridgeIf class
Section.klass(bridge)(bridge, create=True).add_port(self.ifname)
@@ -1337,7 +1419,7 @@ class Interface(Control):
bridge_vlan_filter = Section.klass(bridge)(bridge, create=True).get_vlan_filter()
if int(bridge_vlan_filter):
- cur_vlan_ids = get_vlan_ids(ifname)
+ cur_vlan_ids = get_vlan_ids(self.ifname)
add_vlan = []
native_vlan_id = None
allowed_vlan_ids= []
@@ -1360,15 +1442,15 @@ class Interface(Control):
# Remove redundant VLANs from the system
for vlan in list_diff(cur_vlan_ids, add_vlan):
- cmd = f'bridge vlan del dev {ifname} vid {vlan} master'
+ cmd = f'bridge vlan del dev {self.ifname} vid {vlan} master'
self._cmd(cmd)
for vlan in allowed_vlan_ids:
- cmd = f'bridge vlan add dev {ifname} vid {vlan} master'
+ cmd = f'bridge vlan add dev {self.ifname} vid {vlan} master'
self._cmd(cmd)
# Setting native VLAN to system
if native_vlan_id:
- cmd = f'bridge vlan add dev {ifname} vid {native_vlan_id} pvid untagged master'
+ cmd = f'bridge vlan add dev {self.ifname} vid {native_vlan_id} pvid untagged master'
self._cmd(cmd)
def set_dhcp(self, enable: bool, vrf_changed: bool=False):
@@ -1447,12 +1529,11 @@ class Interface(Control):
if enable not in [True, False]:
raise ValueError()
- ifname = self.ifname
config_base = directories['dhcp6_client_dir']
- config_file = f'{config_base}/dhcp6c.{ifname}.conf'
- script_file = f'/etc/wide-dhcpv6/dhcp6c.{ifname}.script' # can not live under /run b/c of noexec mount option
- systemd_override_file = f'/run/systemd/system/dhcp6c@{ifname}.service.d/10-override.conf'
- systemd_service = f'dhcp6c@{ifname}.service'
+ config_file = f'{config_base}/dhcp6c.{self.ifname}.conf'
+ script_file = f'/etc/wide-dhcpv6/dhcp6c.{self.ifname}.script' # can not live under /run b/c of noexec mount option
+ systemd_override_file = f'/run/systemd/system/dhcp6c@{self.ifname}.service.d/10-override.conf'
+ systemd_service = f'dhcp6c@{self.ifname}.service'
# Rendered client configuration files require additional settings
config = deepcopy(self.config)
@@ -1792,11 +1873,26 @@ class Interface(Control):
value = '0' if (tmp != None) else '1'
self.set_ipv6_forwarding(value)
+ # Delete old interface identifier
+ # This should be before setting the accept_ra value
+ old = dict_search('ipv6.address.interface_identifier_old', config)
+ now = dict_search('ipv6.address.interface_identifier', config)
+ if old and not now:
+ # accept_ra of ra is required to delete the interface identifier
+ self.set_ipv6_accept_ra('2')
+ self.del_ipv6_interface_identifier()
+
+ # Set IPv6 Interface identifier
+ # This should be before setting the accept_ra value
+ tmp = dict_search('ipv6.address.interface_identifier', config)
+ if tmp:
+ # accept_ra is required to set the interface identifier
+ self.set_ipv6_accept_ra('2')
+ self.set_ipv6_interface_identifier(tmp)
+
# IPv6 router advertisements
tmp = dict_search('ipv6.address.autoconf', config)
- value = '2' if (tmp != None) else '1'
- if 'dhcpv6' in new_addr:
- value = '2'
+ value = '2' if (tmp != None) else '0'
self.set_ipv6_accept_ra(value)
# IPv6 address autoconfiguration
diff --git a/python/vyos/kea.py b/python/vyos/kea.py
index 9fc5dde3d..5eecbbaad 100644
--- a/python/vyos/kea.py
+++ b/python/vyos/kea.py
@@ -20,8 +20,8 @@ import socket
from datetime import datetime
from datetime import timezone
+from vyos import ConfigError
from vyos.template import is_ipv6
-from vyos.template import isc_static_route
from vyos.template import netmask_from_cidr
from vyos.utils.dict import dict_search_args
from vyos.utils.file import file_permissions
@@ -44,6 +44,7 @@ kea4_options = {
'wpad_url': 'wpad-url',
'ipv6_only_preferred': 'v6-only-preferred',
'captive_portal': 'v4-captive-portal',
+ 'capwap_controller': 'capwap-ac-v4',
}
kea6_options = {
@@ -56,6 +57,7 @@ kea6_options = {
'nisplus_server': 'nisp-servers',
'sntp_server': 'sntp-servers',
'captive_portal': 'v6-captive-portal',
+ 'capwap_controller': 'capwap-ac-v6',
}
kea_ctrl_socket = '/run/kea/dhcp{inet}-ctrl-socket'
@@ -111,22 +113,21 @@ def kea_parse_options(config):
default_route = ''
if 'default_router' in config:
- default_route = isc_static_route('0.0.0.0/0', config['default_router'])
+ default_route = f'0.0.0.0/0 - {config["default_router"]}'
routes = [
- isc_static_route(route, route_options['next_hop'])
+ f'{route} - {route_options["next_hop"]}'
for route, route_options in config['static_route'].items()
]
options.append(
{
- 'name': 'rfc3442-static-route',
+ 'name': 'classless-static-route',
'data': ', '.join(
routes if not default_route else routes + [default_route]
),
}
)
- options.append({'name': 'windows-static-route', 'data': ', '.join(routes)})
if 'time_zone' in config:
with open('/usr/share/zoneinfo/' + config['time_zone'], 'rb') as f:
@@ -147,7 +148,7 @@ def kea_parse_options(config):
def kea_parse_subnet(subnet, config):
- out = {'subnet': subnet, 'id': int(config['subnet_id'])}
+ out = {'subnet': subnet, 'id': int(config['subnet_id']), 'user-context': {}}
if 'option' in config:
out['option-data'] = kea_parse_options(config['option'])
@@ -165,6 +166,9 @@ def kea_parse_subnet(subnet, config):
out['valid-lifetime'] = int(config['lease'])
out['max-valid-lifetime'] = int(config['lease'])
+ if 'ping_check' in config:
+ out['user-context']['enable-ping-check'] = True
+
if 'range' in config:
pools = []
for num, range_config in config['range'].items():
@@ -218,6 +222,9 @@ def kea_parse_subnet(subnet, config):
reservations.append(reservation)
out['reservations'] = reservations
+ if 'dynamic_dns_update' in config:
+ out.update(kea_parse_ddns_settings(config['dynamic_dns_update']))
+
return out
@@ -347,6 +354,54 @@ def kea6_parse_subnet(subnet, config):
return out
+def kea_parse_tsig_algo(algo_spec):
+ translate = {
+ 'md5': 'HMAC-MD5',
+ 'sha1': 'HMAC-SHA1',
+ 'sha224': 'HMAC-SHA224',
+ 'sha256': 'HMAC-SHA256',
+ 'sha384': 'HMAC-SHA384',
+ 'sha512': 'HMAC-SHA512'
+ }
+ if algo_spec not in translate:
+ raise ConfigError(f'Unsupported TSIG algorithm: {algo_spec}')
+ return translate[algo_spec]
+
+def kea_parse_enable_disable(value):
+ return True if value == 'enable' else False
+
+def kea_parse_ddns_settings(config):
+ data = {}
+
+ if send_updates := config.get('send_updates'):
+ data['ddns-send-updates'] = kea_parse_enable_disable(send_updates)
+
+ if override_client_update := config.get('override_client_update'):
+ data['ddns-override-client-update'] = kea_parse_enable_disable(override_client_update)
+
+ if override_no_update := config.get('override_no_update'):
+ data['ddns-override-no-update'] = kea_parse_enable_disable(override_no_update)
+
+ if update_on_renew := config.get('update_on_renew'):
+ data['ddns-update-on-renew'] = kea_parse_enable_disable(update_on_renew)
+
+ if conflict_resolution := config.get('conflict_resolution'):
+ data['ddns-use-conflict-resolution'] = kea_parse_enable_disable(conflict_resolution)
+
+ if 'replace_client_name' in config:
+ data['ddns-replace-client-name'] = config['replace_client_name']
+ if 'generated_prefix' in config:
+ data['ddns-generated-prefix'] = config['generated_prefix']
+ if 'qualifying_suffix' in config:
+ data['ddns-qualifying-suffix'] = config['qualifying_suffix']
+ if 'ttl_percent' in config:
+ data['ddns-ttl-percent'] = int(config['ttl_percent']) / 100
+ if 'hostname_char_set' in config:
+ data['hostname-char-set'] = config['hostname_char_set']
+ if 'hostname_char_replacement' in config:
+ data['hostname-char-replacement'] = config['hostname_char_replacement']
+
+ return data
def _ctrl_socket_command(inet, command, args=None):
path = kea_ctrl_socket.format(inet=inet)
diff --git a/python/vyos/proto/vyconf_client.py b/python/vyos/proto/vyconf_client.py
index f34549309..b385f0951 100644
--- a/python/vyos/proto/vyconf_client.py
+++ b/python/vyos/proto/vyconf_client.py
@@ -52,7 +52,9 @@ def request_to_msg(req: vyconf_proto.RequestEnvelope) -> vyconf_pb2.RequestEnvel
def msg_to_response(msg: vyconf_pb2.Response) -> vyconf_proto.Response:
# pylint: disable=no-member
- d = MessageToDict(msg, preserving_proto_field_name=True)
+ d = MessageToDict(
+ msg, preserving_proto_field_name=True, use_integers_for_enums=True
+ )
response = vyconf_proto.Response(**d)
return response
diff --git a/python/vyos/system/grub_util.py b/python/vyos/system/grub_util.py
index 4a3d8795e..ad95bb4f9 100644
--- a/python/vyos/system/grub_util.py
+++ b/python/vyos/system/grub_util.py
@@ -56,13 +56,12 @@ def set_kernel_cmdline_options(cmdline_options: str, version: str = '',
@image.if_not_live_boot
def update_kernel_cmdline_options(cmdline_options: str,
- root_dir: str = '') -> None:
+ root_dir: str = '',
+ version = image.get_running_image()) -> None:
"""Update Kernel custom cmdline options"""
if not root_dir:
root_dir = disk.find_persistence()
- version = image.get_running_image()
-
boot_opts_current = grub.get_boot_opts(version, root_dir)
boot_opts_proposed = grub.BOOT_OPTS_STEM + f'{version} {cmdline_options}'
diff --git a/python/vyos/template.py b/python/vyos/template.py
index e75db1a8d..d79e1183f 100755
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -390,28 +390,6 @@ def compare_netmask(netmask1, netmask2):
except:
return False
-@register_filter('isc_static_route')
-def isc_static_route(subnet, router):
- # https://ercpe.de/blog/pushing-static-routes-with-isc-dhcp-server
- # Option format is:
- # <netmask>, <network-byte1>, <network-byte2>, <network-byte3>, <router-byte1>, <router-byte2>, <router-byte3>
- # where bytes with the value 0 are omitted.
- from ipaddress import ip_network
- net = ip_network(subnet)
- # add netmask
- string = str(net.prefixlen) + ','
- # add network bytes
- if net.prefixlen:
- width = net.prefixlen // 8
- if net.prefixlen % 8:
- width += 1
- string += ','.join(map(str,tuple(net.network_address.packed)[:width])) + ','
-
- # add router bytes
- string += ','.join(router.split('.'))
-
- return string
-
@register_filter('is_file')
def is_file(filename):
if os.path.exists(filename):
@@ -881,10 +859,77 @@ def kea_high_availability_json(config):
return dumps(data)
+@register_filter('kea_dynamic_dns_update_main_json')
+def kea_dynamic_dns_update_main_json(config):
+ from vyos.kea import kea_parse_ddns_settings
+ from json import dumps
+
+ data = kea_parse_ddns_settings(config)
+
+ if len(data) == 0:
+ return ''
+
+ return dumps(data, indent=8)[1:-1] + ','
+
+@register_filter('kea_dynamic_dns_update_tsig_key_json')
+def kea_dynamic_dns_update_tsig_key_json(config):
+ from vyos.kea import kea_parse_tsig_algo
+ from json import dumps
+ out = []
+
+ if 'tsig_key' not in config:
+ return dumps(out)
+
+ tsig_keys = config['tsig_key']
+
+ for tsig_key_name, tsig_key_config in tsig_keys.items():
+ tsig_key = {
+ 'name': tsig_key_name,
+ 'algorithm': kea_parse_tsig_algo(tsig_key_config['algorithm']),
+ 'secret': tsig_key_config['secret']
+ }
+ out.append(tsig_key)
+
+ return dumps(out, indent=12)
+
+@register_filter('kea_dynamic_dns_update_domains')
+def kea_dynamic_dns_update_domains(config, type_key):
+ from json import dumps
+ out = []
+
+ if type_key not in config:
+ return dumps(out)
+
+ domains = config[type_key]
+
+ for domain_name, domain_config in domains.items():
+ domain = {
+ 'name': domain_name,
+
+ }
+ if 'key_name' in domain_config:
+ domain['key-name'] = domain_config['key_name']
+
+ if 'dns_server' in domain_config:
+ dns_servers = []
+ for dns_server_config in domain_config['dns_server'].values():
+ dns_server = {
+ 'ip-address': dns_server_config['address']
+ }
+ if 'port' in dns_server_config:
+ dns_server['port'] = int(dns_server_config['port'])
+ dns_servers.append(dns_server)
+ domain['dns-servers'] = dns_servers
+
+ out.append(domain)
+
+ return dumps(out, indent=12)
+
@register_filter('kea_shared_network_json')
def kea_shared_network_json(shared_networks):
from vyos.kea import kea_parse_options
from vyos.kea import kea_parse_subnet
+ from vyos.kea import kea_parse_ddns_settings
from json import dumps
out = []
@@ -895,9 +940,13 @@ def kea_shared_network_json(shared_networks):
network = {
'name': name,
'authoritative': ('authoritative' in config),
- 'subnet4': []
+ 'subnet4': [],
+ 'user-context': {}
}
+ if 'dynamic_dns_update' in config:
+ network.update(kea_parse_ddns_settings(config['dynamic_dns_update']))
+
if 'option' in config:
network['option-data'] = kea_parse_options(config['option'])
@@ -907,6 +956,9 @@ def kea_shared_network_json(shared_networks):
if 'bootfile_server' in config['option']:
network['next-server'] = config['option']['bootfile_server']
+ if 'ping_check' in config:
+ network['user-context']['enable-ping-check'] = True
+
if 'subnet' in config:
for subnet, subnet_config in config['subnet'].items():
if 'disable' in subnet_config:
diff --git a/python/vyos/vyconf_session.py b/python/vyos/vyconf_session.py
new file mode 100644
index 000000000..506095625
--- /dev/null
+++ b/python/vyos/vyconf_session.py
@@ -0,0 +1,123 @@
+# Copyright 2025 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/>.
+#
+#
+
+import tempfile
+import shutil
+from functools import wraps
+from typing import Type
+
+from vyos.proto import vyconf_client
+from vyos.migrate import ConfigMigrate
+from vyos.migrate import ConfigMigrateError
+from vyos.component_version import append_system_version
+
+
+def output(o):
+ out = ''
+ for res in (o.output, o.error, o.warning):
+ if res is not None:
+ out = out + res
+ return out
+
+
+class VyconfSession:
+ def __init__(self, token: str = None, on_error: Type[Exception] = None):
+ if token is None:
+ out = vyconf_client.send_request('setup_session')
+ self.__token = out.output
+ else:
+ self.__token = token
+
+ self.on_error = on_error
+
+ @staticmethod
+ def raise_exception(f):
+ @wraps(f)
+ def wrapped(self, *args, **kwargs):
+ if self.on_error is None:
+ return f(self, *args, **kwargs)
+ o, e = f(self, *args, **kwargs)
+ if e:
+ raise self.on_error(o)
+ return o, e
+
+ return wrapped
+
+ @raise_exception
+ def set(self, path: list[str]) -> tuple[str, int]:
+ out = vyconf_client.send_request('set', token=self.__token, path=path)
+ return output(out), out.status
+
+ @raise_exception
+ def delete(self, path: list[str]) -> tuple[str, int]:
+ out = vyconf_client.send_request('delete', token=self.__token, path=path)
+ return output(out), out.status
+
+ @raise_exception
+ def commit(self) -> tuple[str, int]:
+ out = vyconf_client.send_request('commit', token=self.__token)
+ return output(out), out.status
+
+ @raise_exception
+ def discard(self) -> tuple[str, int]:
+ out = vyconf_client.send_request('discard', token=self.__token)
+ return output(out), out.status
+
+ def session_changed(self) -> bool:
+ out = vyconf_client.send_request('session_changed', token=self.__token)
+ return not bool(out.status)
+
+ @raise_exception
+ def load_config(self, file: str, migrate: bool = False) -> tuple[str, int]:
+ # pylint: disable=consider-using-with
+ if migrate:
+ tmp = tempfile.NamedTemporaryFile()
+ shutil.copy2(file, tmp.name)
+ config_migrate = ConfigMigrate(tmp.name)
+ try:
+ config_migrate.run()
+ except ConfigMigrateError as e:
+ tmp.close()
+ return repr(e), 1
+ file = tmp.name
+ else:
+ tmp = ''
+
+ out = vyconf_client.send_request('load', token=self.__token, location=file)
+ if tmp:
+ tmp.close()
+
+ return output(out), out.status
+
+ @raise_exception
+ def save_config(self, file: str, append_version: bool = False) -> tuple[str, int]:
+ out = vyconf_client.send_request('save', token=self.__token, location=file)
+ if append_version:
+ append_system_version(file)
+ return output(out), out.status
+
+ @raise_exception
+ def show_config(self, path: list[str] = None) -> tuple[str, int]:
+ if path is None:
+ path = []
+ out = vyconf_client.send_request('show_config', token=self.__token, path=path)
+ return output(out), out.status
+
+ def __del__(self):
+ out = vyconf_client.send_request('teardown', token=self.__token)
+ if out.status:
+ print(f'Could not tear down session {self.__token}: {output(out)}')
diff --git a/scripts/build-command-op-templates b/scripts/build-command-op-templates
index d203fdcef..0bb62113e 100755
--- a/scripts/build-command-op-templates
+++ b/scripts/build-command-op-templates
@@ -116,7 +116,7 @@ def get_properties(p):
if comptype is not None:
props["comp_type"] = "imagefiles"
comp_exprs.append("echo -n \"<imagefiles>\"")
- comp_help = " && ".join(comp_exprs)
+ comp_help = " ; ".join(comp_exprs)
props["comp_help"] = comp_help
except:
diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos
index 4793e069e..aaf450e80 100644
--- a/smoketest/config-tests/basic-vyos
+++ b/smoketest/config-tests/basic-vyos
@@ -28,7 +28,21 @@ set protocols static arp interface eth2.200.201 address 100.64.201.20 mac '00:50
set protocols static arp interface eth2.200.202 address 100.64.202.30 mac '00:50:00:00:00:30'
set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50:00:00:00:40'
set protocols static route 0.0.0.0/0 next-hop 100.64.0.1
+set service dhcp-server dynamic-dns-update send-updates 'enable'
+set service dhcp-server dynamic-dns-update conflict-resolution 'enable'
+set service dhcp-server dynamic-dns-update tsig-key domain-lan-updates algorithm 'sha256'
+set service dhcp-server dynamic-dns-update tsig-key domain-lan-updates secret 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ=='
+set service dhcp-server dynamic-dns-update tsig-key reverse-0-168-192 algorithm 'sha256'
+set service dhcp-server dynamic-dns-update tsig-key reverse-0-168-192 secret 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ=='
+set service dhcp-server dynamic-dns-update forward-domain domain.lan dns-server 1 address '192.168.0.1'
+set service dhcp-server dynamic-dns-update forward-domain domain.lan dns-server 2 address '100.100.0.1'
+set service dhcp-server dynamic-dns-update forward-domain domain.lan key-name 'domain-lan-updates'
+set service dhcp-server dynamic-dns-update reverse-domain 0.168.192.in-addr.arpa dns-server 1 address '192.168.0.1'
+set service dhcp-server dynamic-dns-update reverse-domain 0.168.192.in-addr.arpa dns-server 2 address '100.100.0.1'
+set service dhcp-server dynamic-dns-update reverse-domain 0.168.192.in-addr.arpa key-name 'reverse-0-168-192'
set service dhcp-server shared-network-name LAN authoritative
+set service dhcp-server shared-network-name LAN dynamic-dns-update send-updates 'enable'
+set service dhcp-server shared-network-name LAN dynamic-dns-update ttl-percent '75'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-search 'vyos.net'
@@ -46,6 +60,9 @@ set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-map
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 ip-address '192.168.0.21'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 mac '00:01:02:03:04:22'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 subnet-id '1'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update send-updates 'enable'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update generated-prefix 'myhost'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update qualifying-suffix 'lan1.domain.lan'
set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 interface 'eth0'
set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 option domain-search 'vyos.net'
set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 option name-server 'fe88::1'
diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos
index a6cd3b6e1..5f7a71237 100644
--- a/smoketest/configs/basic-vyos
+++ b/smoketest/configs/basic-vyos
@@ -99,33 +99,77 @@ protocols {
}
service {
dhcp-server {
+ dynamic-dns-update {
+ send-updates enable
+ forward-domain domain.lan {
+ dns-server 1 {
+ address 192.168.0.1
+ }
+ dns-server 2 {
+ address 100.100.0.1
+ }
+ key-name domain-lan-updates
+ }
+ reverse-domain 0.168.192.in-addr.arpa {
+ dns-server 1 {
+ address 192.168.0.1
+ }
+ dns-server 2 {
+ address 100.100.0.1
+ }
+ key-name reverse-0-168-192
+ }
+ tsig-key domain-lan-updates {
+ algorithm sha256
+ secret SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==
+ }
+ tsig-key reverse-0-168-192 {
+ algorithm sha256
+ secret VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ==
+ }
+ conflict-resolution enable
+ }
shared-network-name LAN {
authoritative
+ dynamic-dns-update {
+ send-updates enable
+ ttl-percent 75
+ }
subnet 192.168.0.0/24 {
- default-router 192.168.0.1
- dns-server 192.168.0.1
- domain-name vyos.net
- domain-search vyos.net
+ dynamic-dns-update {
+ send-updates enable
+ generated-prefix myhost
+ qualifying-suffix lan1.domain.lan
+ }
+ option {
+ default-router 192.168.0.1
+ domain-name vyos.net
+ domain-search vyos.net
+ name-server 192.168.0.1
+ }
range LANDynamic {
start 192.168.0.30
stop 192.168.0.240
}
static-mapping TEST1-1 {
ip-address 192.168.0.11
- mac-address 00:01:02:03:04:05
+ mac 00:01:02:03:04:05
}
static-mapping TEST1-2 {
+ disable
ip-address 192.168.0.12
- mac-address 00:01:02:03:04:05
+ mac 00:01:02:03:04:05
}
static-mapping TEST2-1 {
ip-address 192.168.0.21
- mac-address 00:01:02:03:04:21
+ mac 00:01:02:03:04:21
}
static-mapping TEST2-2 {
+ disable
ip-address 192.168.0.21
- mac-address 00:01:02:03:04:22
+ mac 00:01:02:03:04:22
}
+ subnet-id 1
}
}
}
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index 3e2653a2f..5348b0cc3 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -14,6 +14,7 @@
import re
+from json import loads
from netifaces import AF_INET
from netifaces import AF_INET6
from netifaces import ifaddresses
@@ -1067,6 +1068,7 @@ class BasicInterfaceTest:
dad_transmits = '10'
accept_dad = '0'
source_validation = 'strict'
+ interface_identifier = '::fffe'
for interface in self._interfaces:
path = self._base_path + [interface]
@@ -1089,6 +1091,9 @@ class BasicInterfaceTest:
if cli_defined(self._base_path + ['ipv6'], 'source-validation'):
self.cli_set(path + ['ipv6', 'source-validation', source_validation])
+ if cli_defined(self._base_path + ['ipv6', 'address'], 'interface-identifier'):
+ self.cli_set(path + ['ipv6', 'address', 'interface-identifier', interface_identifier])
+
self.cli_commit()
for interface in self._interfaces:
@@ -1120,6 +1125,13 @@ class BasicInterfaceTest:
self.assertIn('fib saddr . iif oif 0', line)
self.assertIn('drop', line)
+ if cli_defined(self._base_path + ['ipv6', 'address'], 'interface-identifier'):
+ tmp = cmd(f'ip -j token show dev {interface}')
+ tmp = loads(tmp)[0]
+ self.assertEqual(tmp['token'], interface_identifier)
+ self.assertEqual(tmp['ifname'], interface)
+
+
def test_dhcpv6_client_options(self):
if not self._test_ipv6_dhcpc6:
self.skipTest(MSG_TESTCASE_UNSUPPORTED)
diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
index 36622cad1..daad3a909 100755
--- a/smoketest/scripts/cli/test_container.py
+++ b/smoketest/scripts/cli/test_container.py
@@ -33,11 +33,13 @@ PROCESS_PIDFILE = '/run/vyos-container-{0}.service.pid'
busybox_image = 'busybox:stable'
busybox_image_path = '/usr/share/vyos/busybox-stable.tar'
+
def cmd_to_json(command):
c = cmd(command + ' --format=json')
data = json.loads(c)[0]
return data
+
class TestContainer(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
@@ -73,13 +75,26 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
cont_name = 'c1'
self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '10.0.2.15/24'])
- self.cli_set(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '10.0.2.2'])
+ self.cli_set(
+ ['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '10.0.2.2']
+ )
self.cli_set(['system', 'name-server', '1.1.1.1'])
self.cli_set(['system', 'name-server', '8.8.8.8'])
self.cli_set(base_path + ['name', cont_name, 'image', busybox_image])
self.cli_set(base_path + ['name', cont_name, 'allow-host-networks'])
- self.cli_set(base_path + ['name', cont_name, 'sysctl', 'parameter', 'kernel.msgmax', 'value', '4096'])
+ self.cli_set(
+ base_path
+ + [
+ 'name',
+ cont_name,
+ 'sysctl',
+ 'parameter',
+ 'kernel.msgmax',
+ 'value',
+ '4096',
+ ]
+ )
# commit changes
self.cli_commit()
@@ -95,6 +110,14 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
tmp = cmd(f'sudo podman exec -it {cont_name} sysctl kernel.msgmax')
self.assertEqual(tmp, 'kernel.msgmax = 4096')
+ def test_log_driver(self):
+ self.cli_set(base_path + ['log-driver', 'journald'])
+
+ self.cli_commit()
+
+ tmp = cmd('podman info --format "{{ .Host.LogDriver }}"')
+ self.assertEqual(tmp, 'journald')
+
def test_name_server(self):
cont_name = 'dns-test'
net_name = 'net-test'
@@ -105,7 +128,17 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['name', cont_name, 'image', busybox_image])
self.cli_set(base_path + ['name', cont_name, 'name-server', name_server])
- self.cli_set(base_path + ['name', cont_name, 'network', net_name, 'address', str(ip_interface(prefix).ip + 2)])
+ self.cli_set(
+ base_path
+ + [
+ 'name',
+ cont_name,
+ 'network',
+ net_name,
+ 'address',
+ str(ip_interface(prefix).ip + 2),
+ ]
+ )
# verify() - name server has no effect when container network has dns enabled
with self.assertRaises(ConfigSessionError):
@@ -146,7 +179,17 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
for ii in range(1, 6):
name = f'{base_name}-{ii}'
self.cli_set(base_path + ['name', name, 'image', busybox_image])
- self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)])
+ self.cli_set(
+ base_path
+ + [
+ 'name',
+ name,
+ 'network',
+ net_name,
+ 'address',
+ str(ip_interface(prefix).ip + ii),
+ ]
+ )
# verify() - first IP address of a prefix can not be used by a container
with self.assertRaises(ConfigSessionError):
@@ -163,8 +206,14 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
for ii in range(2, 6):
name = f'{base_name}-{ii}'
c = cmd_to_json(f'sudo podman container inspect {name}')
- self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway'] , str(ip_interface(prefix).ip + 1))
- self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'], str(ip_interface(prefix).ip + ii))
+ self.assertEqual(
+ c['NetworkSettings']['Networks'][net_name]['Gateway'],
+ str(ip_interface(prefix).ip + 1),
+ )
+ self.assertEqual(
+ c['NetworkSettings']['Networks'][net_name]['IPAddress'],
+ str(ip_interface(prefix).ip + ii),
+ )
def test_ipv6_network(self):
prefix = '2001:db8::/64'
@@ -176,7 +225,17 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
for ii in range(1, 6):
name = f'{base_name}-{ii}'
self.cli_set(base_path + ['name', name, 'image', busybox_image])
- self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)])
+ self.cli_set(
+ base_path
+ + [
+ 'name',
+ name,
+ 'network',
+ net_name,
+ 'address',
+ str(ip_interface(prefix).ip + ii),
+ ]
+ )
# verify() - first IP address of a prefix can not be used by a container
with self.assertRaises(ConfigSessionError):
@@ -193,8 +252,14 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
for ii in range(2, 6):
name = f'{base_name}-{ii}'
c = cmd_to_json(f'sudo podman container inspect {name}')
- self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'] , str(ip_interface(prefix).ip + 1))
- self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix).ip + ii))
+ self.assertEqual(
+ c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'],
+ str(ip_interface(prefix).ip + 1),
+ )
+ self.assertEqual(
+ c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'],
+ str(ip_interface(prefix).ip + ii),
+ )
def test_dual_stack_network(self):
prefix4 = '192.0.2.0/24'
@@ -208,8 +273,28 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
for ii in range(1, 6):
name = f'{base_name}-{ii}'
self.cli_set(base_path + ['name', name, 'image', busybox_image])
- self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix4).ip + ii)])
- self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix6).ip + ii)])
+ self.cli_set(
+ base_path
+ + [
+ 'name',
+ name,
+ 'network',
+ net_name,
+ 'address',
+ str(ip_interface(prefix4).ip + ii),
+ ]
+ )
+ self.cli_set(
+ base_path
+ + [
+ 'name',
+ name,
+ 'network',
+ net_name,
+ 'address',
+ str(ip_interface(prefix6).ip + ii),
+ ]
+ )
# verify() - first IP address of a prefix can not be used by a container
with self.assertRaises(ConfigSessionError):
@@ -227,10 +312,22 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
for ii in range(2, 6):
name = f'{base_name}-{ii}'
c = cmd_to_json(f'sudo podman container inspect {name}')
- self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'] , str(ip_interface(prefix6).ip + 1))
- self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix6).ip + ii))
- self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway'] , str(ip_interface(prefix4).ip + 1))
- self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'] , str(ip_interface(prefix4).ip + ii))
+ self.assertEqual(
+ c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'],
+ str(ip_interface(prefix6).ip + 1),
+ )
+ self.assertEqual(
+ c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'],
+ str(ip_interface(prefix6).ip + ii),
+ )
+ self.assertEqual(
+ c['NetworkSettings']['Networks'][net_name]['Gateway'],
+ str(ip_interface(prefix4).ip + 1),
+ )
+ self.assertEqual(
+ c['NetworkSettings']['Networks'][net_name]['IPAddress'],
+ str(ip_interface(prefix4).ip + ii),
+ )
def test_no_name_server(self):
prefix = '192.0.2.0/24'
@@ -242,7 +339,17 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
name = f'{base_name}-2'
self.cli_set(base_path + ['name', name, 'image', busybox_image])
- self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + 2)])
+ self.cli_set(
+ base_path
+ + [
+ 'name',
+ name,
+ 'network',
+ net_name,
+ 'address',
+ str(ip_interface(prefix).ip + 2),
+ ]
+ )
self.cli_commit()
n = cmd_to_json(f'sudo podman network inspect {net_name}')
@@ -258,7 +365,17 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
name = f'{base_name}-2'
self.cli_set(base_path + ['name', name, 'image', busybox_image])
- self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + 2)])
+ self.cli_set(
+ base_path
+ + [
+ 'name',
+ name,
+ 'network',
+ net_name,
+ 'address',
+ str(ip_interface(prefix).ip + 2),
+ ]
+ )
self.cli_commit()
n = cmd_to_json(f'sudo podman network inspect {net_name}')
@@ -298,11 +415,14 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Query API about running containers
- tmp = cmd("sudo curl --unix-socket /run/podman/podman.sock -H 'content-type: application/json' -sf http://localhost/containers/json")
+ tmp = cmd(
+ "sudo curl --unix-socket /run/podman/podman.sock -H 'content-type: application/json' -sf http://localhost/containers/json"
+ )
tmp = json.loads(tmp)
# We expect the same amount of containers from the API that we started above
self.assertEqual(len(container_list), len(tmp))
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 2829edbfb..69de0c326 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -642,6 +642,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
def test_ipv4_global_state(self):
+ self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0'])
+ self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software'])
+
+ self.cli_set(['firewall', 'global-options', 'state-policy', 'offload', 'offload-target', 'smoketest'])
self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept'])
self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept'])
self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop'])
@@ -651,6 +655,9 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['jump VYOS_STATE_POLICY'],
['chain VYOS_STATE_POLICY'],
+ ['jump VYOS_STATE_POLICY_FORWARD'],
+ ['chain VYOS_STATE_POLICY_FORWARD'],
+ ['flow add @VYOS_FLOWTABLE_smoketest'],
['ct state established', 'accept'],
['ct state invalid', 'drop'],
['ct state related', 'accept']
diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py
index 694c24e4d..132496124 100755
--- a/smoketest/scripts/cli/test_interfaces_vxlan.py
+++ b/smoketest/scripts/cli/test_interfaces_vxlan.py
@@ -125,19 +125,17 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
'source-interface eth0',
'vni 60'
]
- params = []
for option in options:
opts = option.split()
- params.append(opts[0])
- self.cli_set(self._base_path + [ intf ] + opts)
+ self.cli_set(self._base_path + [intf] + opts)
- with self.assertRaises(ConfigSessionError) as cm:
+ # verify() - Both group and remote cannot be specified
+ with self.assertRaises(ConfigSessionError):
self.cli_commit()
- exception = cm.exception
- self.assertIn('Both group and remote cannot be specified', str(exception))
- for param in params:
- self.cli_delete(self._base_path + [intf, param])
+ # Remove blocking CLI option
+ self.cli_delete(self._base_path + [intf, 'group'])
+ self.cli_commit()
def test_vxlan_external(self):
diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py
index 53761b7d6..15ddd857e 100755
--- a/smoketest/scripts/cli/test_policy_route.py
+++ b/smoketest/scripts/cli/test_policy_route.py
@@ -307,5 +307,39 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables6_search, 'ip6 vyos_mangle')
+ def test_geoip(self):
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'action', 'drop'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'gb'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'action', 'accept'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'de'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'fr'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'source', 'geoip', 'inverse-match'])
+
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'action', 'drop'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'source', 'geoip', 'country-code', 'se'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'source', 'geoip', 'country-code', 'gb'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'action', 'accept'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'source', 'geoip', 'country-code', 'de'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'source', 'geoip', 'country-code', 'fr'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'source', 'geoip', 'inverse-match'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['ip saddr @GEOIP_CC_route_smoketest_1', 'drop'],
+ ['ip saddr != @GEOIP_CC_route_smoketest_2', 'accept'],
+ ]
+
+ # -t prevents 1000+ GeoIP elements being returned
+ self.verify_nftables(nftables_search, 'ip vyos_mangle', args='-t')
+
+ nftables_search = [
+ ['ip6 saddr @GEOIP_CC6_route6_smoketest6_1', 'drop'],
+ ['ip6 saddr != @GEOIP_CC6_route6_smoketest6_2', 'accept'],
+ ]
+
+ self.verify_nftables(nftables_search, 'ip6 vyos_mangle', args='-t')
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py
index 7c2ebff89..e421f04d2 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -32,8 +32,10 @@ from vyos.template import inc_ip
from vyos.template import dec_ip
PROCESS_NAME = 'kea-dhcp4'
+D2_PROCESS_NAME = 'kea-dhcp-ddns'
CTRL_PROCESS_NAME = 'kea-ctrl-agent'
KEA4_CONF = '/run/kea/kea-dhcp4.conf'
+KEA4_D2_CONF = '/run/kea/kea-dhcp-ddns.conf'
KEA4_CTRL = '/run/kea/dhcp4-ctrl-socket'
HOSTSD_CLIENT = '/usr/bin/vyos-hostsd-client'
base_path = ['service', 'dhcp-server']
@@ -96,6 +98,10 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.assertTrue(key in base_obj)
self.assertEqual(base_obj[key], value)
+ def verify_service_running(self):
+ tmp = cmd('tail -n 100 /var/log/messages | grep kea')
+ self.assertTrue(process_named_running(PROCESS_NAME), msg=f'Service not running, log: {tmp}')
+
def test_dhcp_single_pool_range(self):
shared_net_name = 'SMOKE-1'
@@ -106,9 +112,12 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['listen-interface', interface])
+ self.cli_set(base_path + ['shared-network-name', shared_net_name, 'ping-check'])
+
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
self.cli_set(pool + ['subnet-id', '1'])
self.cli_set(pool + ['ignore-client-id'])
+ self.cli_set(pool + ['ping-check'])
# we use the first subnet IP address as default gateway
self.cli_set(pool + ['option', 'default-router', router])
self.cli_set(pool + ['option', 'name-server', dns_1])
@@ -151,6 +160,21 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400
)
+ # Verify ping-check
+ self.verify_config_value(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'user-context'],
+ 'enable-ping-check',
+ True
+ )
+
+ self.verify_config_value(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'user-context'],
+ 'enable-ping-check',
+ True
+ )
+
# Verify options
self.verify_config_object(
obj,
@@ -181,7 +205,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
)
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.verify_service_running()
def test_dhcp_single_pool_options(self):
shared_net_name = 'SMOKE-0815'
@@ -197,6 +221,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
wpad = 'http://wpad.vyos.io/foo/bar'
server_identifier = bootfile_server
ipv6_only_preferred = '300'
+ capwap_access_controller = '192.168.2.125'
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
self.cli_set(pool + ['subnet-id', '1'])
@@ -216,9 +241,15 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.cli_set(pool + ['option', 'bootfile-server', bootfile_server])
self.cli_set(pool + ['option', 'wpad-url', wpad])
self.cli_set(pool + ['option', 'server-identifier', server_identifier])
+ self.cli_set(
+ pool + ['option', 'capwap-controller', capwap_access_controller]
+ )
+
+ static_route = '10.0.0.0/24'
+ static_route_nexthop = '192.0.2.1'
self.cli_set(
- pool + ['option', 'static-route', '10.0.0.0/24', 'next-hop', '192.0.2.1']
+ pool + ['option', 'static-route', static_route, 'next-hop', static_route_nexthop]
)
self.cli_set(pool + ['option', 'ipv6-only-preferred', ipv6_only_preferred])
self.cli_set(pool + ['option', 'time-zone', 'Europe/London'])
@@ -301,25 +332,25 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.verify_config_object(
obj,
['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'tftp-server-name', 'data': tftp_server},
+ {'name': 'capwap-ac-v4', 'data': capwap_access_controller},
)
self.verify_config_object(
obj,
['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'wpad-url', 'data': wpad},
+ {'name': 'tftp-server-name', 'data': tftp_server},
)
self.verify_config_object(
obj,
['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {
- 'name': 'rfc3442-static-route',
- 'data': '24,10,0,0,192,0,2,1, 0,192,0,2,1',
- },
+ {'name': 'wpad-url', 'data': wpad},
)
self.verify_config_object(
obj,
['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
- {'name': 'windows-static-route', 'data': '24,10,0,0,192,0,2,1'},
+ {
+ 'name': 'classless-static-route',
+ 'data': f'{static_route} - {static_route_nexthop}, 0.0.0.0/0 - {router}',
+ },
)
self.verify_config_object(
obj,
@@ -352,7 +383,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
)
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.verify_service_running()
def test_dhcp_single_pool_options_scoped(self):
shared_net_name = 'SMOKE-2'
@@ -438,7 +469,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
)
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.verify_service_running()
def test_dhcp_single_pool_static_mapping(self):
shared_net_name = 'SMOKE-2'
@@ -584,7 +615,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
client_base += 1
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.verify_service_running()
def test_dhcp_multiple_pools(self):
lease_time = '14400'
@@ -726,7 +757,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
client_base += 1
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.verify_service_running()
def test_dhcp_exclude_not_in_range(self):
# T3180: verify else path when slicing DHCP ranges and exclude address
@@ -773,7 +804,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
)
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.verify_service_running()
def test_dhcp_exclude_in_range(self):
# T3180: verify else path when slicing DHCP ranges and exclude address
@@ -836,7 +867,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
)
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.verify_service_running()
def test_dhcp_relay_server(self):
# Listen on specific address and return DHCP leases from a non
@@ -884,7 +915,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
)
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.verify_service_running()
def test_dhcp_high_availability(self):
shared_net_name = 'FAILOVER'
@@ -987,8 +1018,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
)
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
- self.assertTrue(process_named_running(CTRL_PROCESS_NAME))
+ self.verify_service_running()
def test_dhcp_high_availability_standby(self):
shared_net_name = 'FAILOVER'
@@ -1087,8 +1117,134 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
)
# Check for running process
+ self.verify_service_running()
+
+ def test_dhcp_dynamic_dns_update(self):
+ shared_net_name = 'SMOKE-1DDNS'
+
+ range_0_start = inc_ip(subnet, 10)
+ range_0_stop = inc_ip(subnet, 20)
+
+ self.cli_set(base_path + ['listen-interface', interface])
+
+ ddns = base_path + ['dynamic-dns-update']
+
+ self.cli_set(ddns + ['send-updates', 'enable'])
+ self.cli_set(ddns + ['conflict-resolution', 'enable'])
+ self.cli_set(ddns + ['override-no-update', 'enable'])
+ self.cli_set(ddns + ['override-client-update', 'enable'])
+ self.cli_set(ddns + ['replace-client-name', 'always'])
+ self.cli_set(ddns + ['update-on-renew', 'enable'])
+
+ self.cli_set(ddns + ['tsig-key', 'domain-lan-updates', 'algorithm', 'sha256'])
+ self.cli_set(ddns + ['tsig-key', 'domain-lan-updates', 'secret', 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ=='])
+ self.cli_set(ddns + ['tsig-key', 'reverse-0-168-192', 'algorithm', 'sha256'])
+ self.cli_set(ddns + ['tsig-key', 'reverse-0-168-192', 'secret', 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ=='])
+ self.cli_set(ddns + ['forward-domain', 'domain.lan', 'dns-server', '1', 'address', '192.168.0.1'])
+ self.cli_set(ddns + ['forward-domain', 'domain.lan', 'dns-server', '2', 'address', '100.100.0.1'])
+ self.cli_set(ddns + ['forward-domain', 'domain.lan', 'key-name', 'domain-lan-updates'])
+ self.cli_set(ddns + ['reverse-domain', '0.168.192.in-addr.arpa', 'dns-server', '1', 'address', '192.168.0.1'])
+ self.cli_set(ddns + ['reverse-domain', '0.168.192.in-addr.arpa', 'dns-server', '1', 'port', '1053'])
+ self.cli_set(ddns + ['reverse-domain', '0.168.192.in-addr.arpa', 'dns-server', '2', 'address', '100.100.0.1'])
+ self.cli_set(ddns + ['reverse-domain', '0.168.192.in-addr.arpa', 'dns-server', '2', 'port', '1153'])
+ self.cli_set(ddns + ['reverse-domain', '0.168.192.in-addr.arpa', 'key-name', 'reverse-0-168-192'])
+
+ shared = base_path + ['shared-network-name', shared_net_name]
+
+ self.cli_set(shared + ['dynamic-dns-update', 'send-updates', 'enable'])
+ self.cli_set(shared + ['dynamic-dns-update', 'conflict-resolution', 'enable'])
+ self.cli_set(shared + ['dynamic-dns-update', 'ttl-percent', '75'])
+
+ pool = shared + [ 'subnet', subnet]
+
+ self.cli_set(pool + ['subnet-id', '1'])
+
+ self.cli_set(pool + ['range', '0', 'start', range_0_start])
+ self.cli_set(pool + ['range', '0', 'stop', range_0_stop])
+
+ self.cli_set(pool + ['dynamic-dns-update', 'send-updates', 'enable'])
+ self.cli_set(pool + ['dynamic-dns-update', 'generated-prefix', 'myfunnyprefix'])
+ self.cli_set(pool + ['dynamic-dns-update', 'qualifying-suffix', 'suffix.lan'])
+ self.cli_set(pool + ['dynamic-dns-update', 'hostname-char-set', 'xXyYzZ'])
+ self.cli_set(pool + ['dynamic-dns-update', 'hostname-char-replacement', '_xXx_'])
+
+ self.cli_commit()
+
+ config = read_file(KEA4_CONF)
+ d2_config = read_file(KEA4_D2_CONF)
+
+ obj = loads(config)
+ d2_obj = loads(d2_config)
+
+ # Verify global DDNS parameters in the main config file
+ self.verify_config_value(
+ obj,
+ ['Dhcp4'], 'dhcp-ddns',
+ {'enable-updates': True, 'server-ip': '127.0.0.1', 'server-port': 53001, 'sender-ip': '', 'sender-port': 0,
+ 'max-queue-size': 1024, 'ncr-protocol': 'UDP', 'ncr-format': 'JSON'})
+
+ self.verify_config_value(obj, ['Dhcp4'], 'ddns-send-updates', True)
+ self.verify_config_value(obj, ['Dhcp4'], 'ddns-use-conflict-resolution', True)
+ self.verify_config_value(obj, ['Dhcp4'], 'ddns-override-no-update', True)
+ self.verify_config_value(obj, ['Dhcp4'], 'ddns-override-client-update', True)
+ self.verify_config_value(obj, ['Dhcp4'], 'ddns-replace-client-name', 'always')
+ self.verify_config_value(obj, ['Dhcp4'], 'ddns-update-on-renew', True)
+
+ # Verify scoped DDNS parameters in the main config file
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'ddns-send-updates', True)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'ddns-use-conflict-resolution', True)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'ddns-ttl-percent', 0.75)
+
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-send-updates', True)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-generated-prefix', 'myfunnyprefix')
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-qualifying-suffix', 'suffix.lan')
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'hostname-char-set', 'xXyYzZ')
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'hostname-char-replacement', '_xXx_')
+
+ # Verify keys and domains configuration in the D2 config
+ self.verify_config_object(
+ d2_obj,
+ ['DhcpDdns', 'tsig-keys'],
+ {'name': 'domain-lan-updates', 'algorithm': 'HMAC-SHA256', 'secret': 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ=='}
+ )
+ self.verify_config_object(
+ d2_obj,
+ ['DhcpDdns', 'tsig-keys'],
+ {'name': 'reverse-0-168-192', 'algorithm': 'HMAC-SHA256', 'secret': 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ=='}
+ )
+
+ self.verify_config_value(d2_obj, ['DhcpDdns', 'forward-ddns', 'ddns-domains', 0], 'name', 'domain.lan')
+ self.verify_config_value(d2_obj, ['DhcpDdns', 'forward-ddns', 'ddns-domains', 0], 'key-name', 'domain-lan-updates')
+ self.verify_config_object(
+ d2_obj,
+ ['DhcpDdns', 'forward-ddns', 'ddns-domains', 0, 'dns-servers'],
+ {'ip-address': '192.168.0.1'}
+ )
+ self.verify_config_object(
+ d2_obj,
+ ['DhcpDdns', 'forward-ddns', 'ddns-domains', 0, 'dns-servers'],
+ {'ip-address': '100.100.0.1'}
+ )
+
+ self.verify_config_value(d2_obj, ['DhcpDdns', 'reverse-ddns', 'ddns-domains', 0], 'name', '0.168.192.in-addr.arpa')
+ self.verify_config_value(d2_obj, ['DhcpDdns', 'reverse-ddns', 'ddns-domains', 0], 'key-name', 'reverse-0-168-192')
+ self.verify_config_object(
+ d2_obj,
+ ['DhcpDdns', 'reverse-ddns', 'ddns-domains', 0, 'dns-servers'],
+ {'ip-address': '192.168.0.1', 'port': 1053}
+ )
+ self.verify_config_object(
+ d2_obj,
+ ['DhcpDdns', 'reverse-ddns', 'ddns-domains', 0, 'dns-servers'],
+ {'ip-address': '100.100.0.1', 'port': 1153}
+ )
+
+ # Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
- self.assertTrue(process_named_running(CTRL_PROCESS_NAME))
+ self.assertTrue(process_named_running(D2_PROCESS_NAME))
def test_dhcp_on_interface_with_vrf(self):
self.cli_set(['interfaces', 'ethernet', 'eth1', 'address', '10.1.1.1/30'])
@@ -1250,7 +1406,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
)
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.verify_service_running()
# All up and running, now test vyos-hostsd store
diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py
index 6ecf6c1cf..6535ca72d 100755
--- a/smoketest/scripts/cli/test_service_dhcpv6-server.py
+++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py
@@ -108,6 +108,7 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
self.cli_set(pool + ['lease-time', 'default', lease_time])
self.cli_set(pool + ['lease-time', 'maximum', max_lease_time])
self.cli_set(pool + ['lease-time', 'minimum', min_lease_time])
+ self.cli_set(pool + ['option', 'capwap-controller', dns_1])
self.cli_set(pool + ['option', 'name-server', dns_1])
self.cli_set(pool + ['option', 'name-server', dns_2])
self.cli_set(pool + ['option', 'name-server', dns_2])
@@ -157,6 +158,10 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
self.verify_config_object(
obj,
['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'capwap-ac-v6', 'data': dns_1})
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
{'name': 'dns-servers', 'data': f'{dns_1}, {dns_2}'})
self.verify_config_object(
obj,
diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py
index 04c4a2e51..b4fe35d81 100755
--- a/smoketest/scripts/cli/test_service_https.py
+++ b/smoketest/scripts/cli/test_service_https.py
@@ -16,6 +16,7 @@
import unittest
import json
+import psutil
from requests import request
from urllib3.exceptions import InsecureRequestWarning
@@ -113,6 +114,29 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase):
# Check for stopped process
self.assertFalse(process_named_running(PROCESS_NAME))
+ def test_listen_address(self):
+ test_prefix = ['192.0.2.1/26', '2001:db8:1::ffff/64']
+ test_addr = [ i.split('/')[0] for i in test_prefix ]
+ for i, addr in enumerate(test_prefix):
+ self.cli_set(['interfaces', 'dummy', f'dum{i}', 'address', addr])
+
+ key = 'MySuperSecretVyOS'
+ self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key])
+ # commit base config first, for testing update of listen-address
+ self.cli_commit()
+
+ for addr in test_addr:
+ self.cli_set(base_path + ['listen-address', addr])
+ self.cli_commit()
+
+ res = set()
+ t = psutil.net_connections(kind="tcp")
+ for c in t:
+ if c.laddr.port == 443:
+ res.add(c.laddr.ip)
+
+ self.assertEqual(res, set(test_addr))
+
def test_certificate(self):
cert_name = 'test_https'
dh_name = 'dh-test'
diff --git a/smoketest/scripts/cli/test_service_ids_ddos-protection.py b/smoketest/scripts/cli/test_service_ids_ddos-protection.py
deleted file mode 100755
index 91b056eea..000000000
--- a/smoketest/scripts/cli/test_service_ids_ddos-protection.py
+++ /dev/null
@@ -1,116 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2022 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 base_vyostest_shim import VyOSUnitTestSHIM
-
-from vyos.configsession import ConfigSessionError
-from vyos.utils.process import process_named_running
-from vyos.utils.file import read_file
-
-PROCESS_NAME = 'fastnetmon'
-FASTNETMON_CONF = '/run/fastnetmon/fastnetmon.conf'
-NETWORKS_CONF = '/run/fastnetmon/networks_list'
-EXCLUDED_NETWORKS_CONF = '/run/fastnetmon/excluded_networks_list'
-base_path = ['service', 'ids', 'ddos-protection']
-
-class TestServiceIDS(VyOSUnitTestSHIM.TestCase):
- @classmethod
- def setUpClass(cls):
- super(TestServiceIDS, cls).setUpClass()
-
- # ensure we can also run this test on a live system - so lets clean
- # out the current configuration :)
- cls.cli_delete(cls, base_path)
-
- def tearDown(self):
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
-
- # delete test config
- self.cli_delete(base_path)
- self.cli_commit()
-
- self.assertFalse(os.path.exists(FASTNETMON_CONF))
- self.assertFalse(process_named_running(PROCESS_NAME))
-
- def test_fastnetmon(self):
- networks = ['10.0.0.0/24', '10.5.5.0/24', '2001:db8:10::/64', '2001:db8:20::/64']
- excluded_networks = ['10.0.0.1/32', '2001:db8:10::1/128']
- interfaces = ['eth0', 'eth1']
- fps = '3500'
- mbps = '300'
- pps = '60000'
-
- self.cli_set(base_path + ['mode', 'mirror'])
- # Required network!
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
- for tmp in networks:
- self.cli_set(base_path + ['network', tmp])
-
- # optional excluded-network!
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
- for tmp in excluded_networks:
- self.cli_set(base_path + ['excluded-network', tmp])
-
- # Required interface(s)!
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
- for tmp in interfaces:
- self.cli_set(base_path + ['listen-interface', tmp])
-
- self.cli_set(base_path + ['direction', 'in'])
- self.cli_set(base_path + ['threshold', 'general', 'fps', fps])
- self.cli_set(base_path + ['threshold', 'general', 'pps', pps])
- self.cli_set(base_path + ['threshold', 'general', 'mbps', mbps])
-
- # commit changes
- self.cli_commit()
-
- # Check configured port
- config = read_file(FASTNETMON_CONF)
- self.assertIn(f'mirror_afpacket = on', config)
- self.assertIn(f'process_incoming_traffic = on', config)
- self.assertIn(f'process_outgoing_traffic = off', config)
- self.assertIn(f'ban_for_flows = on', config)
- self.assertIn(f'threshold_flows = {fps}', config)
- self.assertIn(f'ban_for_bandwidth = on', config)
- self.assertIn(f'threshold_mbps = {mbps}', config)
- self.assertIn(f'ban_for_pps = on', config)
- self.assertIn(f'threshold_pps = {pps}', config)
- # default
- self.assertIn(f'enable_ban = on', config)
- self.assertIn(f'enable_ban_ipv6 = on', config)
- self.assertIn(f'ban_time = 1900', config)
-
- tmp = ','.join(interfaces)
- self.assertIn(f'interfaces = {tmp}', config)
-
-
- network_config = read_file(NETWORKS_CONF)
- for tmp in networks:
- self.assertIn(f'{tmp}', network_config)
-
- excluded_network_config = read_file(EXCLUDED_NETWORKS_CONF)
- for tmp in excluded_networks:
- self.assertIn(f'{tmp}', excluded_network_config)
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py
index 6dbb6add4..83342bc72 100755
--- a/smoketest/scripts/cli/test_service_router-advert.py
+++ b/smoketest/scripts/cli/test_service_router-advert.py
@@ -252,6 +252,49 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase):
tmp = get_config_value('AdvIntervalOpt')
self.assertEqual(tmp, 'off')
+ def test_auto_ignore(self):
+ isp_prefix = '2001:db8::/64'
+ ula_prefixes = ['fd00::/64', 'fd01::/64']
+
+ self.cli_set(base_path + ['auto-ignore', isp_prefix])
+
+ for ula_prefix in ula_prefixes:
+ self.cli_set(base_path + ['auto-ignore', ula_prefix])
+
+ # commit changes
+ self.cli_commit()
+ config = read_file(RADVD_CONF)
+
+ # ensure autoignoreprefixes block is generated in config file
+ tmp = f'autoignoreprefixes' + ' {'
+ self.assertIn(tmp, config)
+
+ # ensure all three prefixes are contained in the block
+ self.assertIn(f' {isp_prefix};', config)
+ for ula_prefix in ula_prefixes:
+ self.assertIn(f' {ula_prefix};', config)
+
+ # remove a prefix and verify it's gone
+ self.cli_delete(base_path + ['auto-ignore', ula_prefixes[1]])
+
+ self.cli_commit()
+ config = read_file(RADVD_CONF)
+
+ self.assertNotIn(f' {ula_prefixes[1]};', config)
+
+ # ensure remaining two prefixes are still present
+ self.assertIn(f' {ula_prefixes[0]};', config)
+ self.assertIn(f' {isp_prefix};', config)
+
+ # remove the remaining two prefixes and verify the config block is gone
+ self.cli_delete(base_path + ['auto-ignore', ula_prefixes[0]])
+ self.cli_delete(base_path + ['auto-ignore', isp_prefix])
+
+ self.cli_commit()
+ config = read_file(RADVD_CONF)
+
+ tmp = f'autoignoreprefixes' + ' {'
+ self.assertNotIn(tmp, config)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_option.py b/smoketest/scripts/cli/test_system_option.py
index f3112cf0b..4daa812c0 100755
--- a/smoketest/scripts/cli/test_system_option.py
+++ b/smoketest/scripts/cli/test_system_option.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2024 VyOS maintainers and contributors
+# Copyright (C) 2024-2025 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -16,14 +16,18 @@
import os
import unittest
+
from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSessionError
+from vyos.utils.cpu import get_cpus
from vyos.utils.file import read_file
from vyos.utils.process import is_systemd_service_active
from vyos.utils.system import sysctl_read
+from vyos.system import image
base_path = ['system', 'option']
-
class TestSystemOption(VyOSUnitTestSHIM.TestCase):
def tearDown(self):
self.cli_delete(base_path)
@@ -96,6 +100,30 @@ class TestSystemOption(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.assertFalse(os.path.exists(ssh_client_opt_file))
+ def test_kernel_options(self):
+ amd_pstate_mode = 'active'
+
+ self.cli_set(['system', 'option', 'kernel', 'disable-mitigations'])
+ self.cli_set(['system', 'option', 'kernel', 'disable-power-saving'])
+ self.cli_set(['system', 'option', 'kernel', 'quiet'])
+
+ self.cli_set(['system', 'option', 'kernel', 'amd-pstate-driver', amd_pstate_mode])
+ cpu_vendor = get_cpus()[0]['vendor_id']
+ if cpu_vendor != 'AuthenticAMD':
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(['system', 'option', 'kernel', 'amd-pstate-driver'])
+
+ self.cli_commit()
+
+ # Read GRUB config file for current running image
+ tmp = read_file(f'{image.grub.GRUB_DIR_VYOS_VERS}/{image.get_running_image()}.cfg')
+ self.assertIn(' mitigations=off', tmp)
+ self.assertIn(' intel_idle.max_cstate=0 processor.max_cstate=1', tmp)
+ self.assertIn(' quiet', tmp)
+
+ if cpu_vendor == 'AuthenticAMD':
+ self.assertIn(f' initcall_blacklist=acpi_cpufreq_init amd_pstate={amd_pstate_mode}', tmp)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_syslog.py b/smoketest/scripts/cli/test_system_syslog.py
index 6eae3f19d..f3e1f65ea 100755
--- a/smoketest/scripts/cli/test_system_syslog.py
+++ b/smoketest/scripts/cli/test_system_syslog.py
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
@@ -59,6 +60,11 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ # The default syslog implementation should make syslog.service a
+ # symlink to itself
+ self.assertEqual(os.readlink('/etc/systemd/system/syslog.service'),
+ '/lib/systemd/system/rsyslog.service')
+
# Check for running process
self.assertFalse(process_named_running(PROCESS_NAME))
diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py
index 91a76e6f6..c1d943bde 100755
--- a/smoketest/scripts/cli/test_vpn_ipsec.py
+++ b/smoketest/scripts/cli/test_vpn_ipsec.py
@@ -352,6 +352,94 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
self.tearDownPKI()
+ def test_site_to_site_vti_ts_afi(self):
+ local_address = '192.0.2.10'
+ vti = 'vti10'
+ # IKE
+ self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'disable-mobike'])
+ # ESP
+ self.cli_set(base_path + ['esp-group', esp_group, 'compression'])
+ # VTI interface
+ self.cli_set(vti_path + [vti, 'address', '10.1.1.1/24'])
+
+ # vpn ipsec auth psk <tag> id <x.x.x.x>
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip])
+ self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret])
+
+ # Site to site
+ peer_base_path = base_path + ['site-to-site', 'peer', connection_name]
+ self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
+ self.cli_set(peer_base_path + ['connection-type', 'none'])
+ self.cli_set(peer_base_path + ['force-udp-encapsulation'])
+ self.cli_set(peer_base_path + ['ike-group', ike_group])
+ self.cli_set(peer_base_path + ['default-esp-group', esp_group])
+ self.cli_set(peer_base_path + ['local-address', local_address])
+ self.cli_set(peer_base_path + ['remote-address', peer_ip])
+ self.cli_set(peer_base_path + ['vti', 'bind', vti])
+ self.cli_set(peer_base_path + ['vti', 'esp-group', esp_group])
+ self.cli_set(peer_base_path + ['vti', 'traffic-selector', 'local', 'prefix', '0.0.0.0/0'])
+ self.cli_set(peer_base_path + ['vti', 'traffic-selector', 'remote', 'prefix', '192.0.2.1/32'])
+ self.cli_set(peer_base_path + ['vti', 'traffic-selector', 'remote', 'prefix', '192.0.2.3/32'])
+
+ self.cli_commit()
+
+ swanctl_conf = read_file(swanctl_file)
+ if_id = vti.lstrip('vti')
+ # The key defaults to 0 and will match any policies which similarly do
+ # not have a lookup key configuration - thus we shift the key by one
+ # to also support a vti0 interface
+ if_id = str(int(if_id) +1)
+ swanctl_conf_lines = [
+ f'version = 2',
+ f'auth = psk',
+ f'proposals = aes128-sha1-modp1024',
+ f'esp_proposals = aes128-sha1-modp1024',
+ f'local_addrs = {local_address} # dhcp:no',
+ f'mobike = no',
+ f'remote_addrs = {peer_ip}',
+ f'mode = tunnel',
+ f'local_ts = 0.0.0.0/0',
+ f'remote_ts = 192.0.2.1/32,192.0.2.3/32',
+ f'ipcomp = yes',
+ f'start_action = none',
+ f'replay_window = 32',
+ f'if_id_in = {if_id}', # will be 11 for vti10 - shifted by one
+ f'if_id_out = {if_id}',
+ f'updown = "/etc/ipsec.d/vti-up-down {vti}"'
+ ]
+ for line in swanctl_conf_lines:
+ self.assertIn(line, swanctl_conf)
+
+ # Check IPv6 TS
+ self.cli_delete(peer_base_path + ['vti', 'traffic-selector'])
+ self.cli_set(peer_base_path + ['vti', 'traffic-selector', 'local', 'prefix', '::/0'])
+ self.cli_set(peer_base_path + ['vti', 'traffic-selector', 'remote', 'prefix', '::/0'])
+ self.cli_commit()
+ swanctl_conf = read_file(swanctl_file)
+ swanctl_conf_lines = [
+ f'local_ts = ::/0',
+ f'remote_ts = ::/0',
+ f'updown = "/etc/ipsec.d/vti-up-down {vti}"'
+ ]
+ for line in swanctl_conf_lines:
+ self.assertIn(line, swanctl_conf)
+
+ # Check both TS (IPv4 + IPv6)
+ self.cli_delete(peer_base_path + ['vti', 'traffic-selector'])
+ self.cli_commit()
+ swanctl_conf = read_file(swanctl_file)
+ swanctl_conf_lines = [
+ f'local_ts = 0.0.0.0/0,::/0',
+ f'remote_ts = 0.0.0.0/0,::/0',
+ f'updown = "/etc/ipsec.d/vti-up-down {vti}"'
+ ]
+ for line in swanctl_conf_lines:
+ self.assertIn(line, swanctl_conf)
+
+
def test_dmvpn(self):
ike_lifetime = '3600'
esp_lifetime = '1800'
diff --git a/smoketest/scripts/system/test_kernel_options.py b/smoketest/scripts/system/test_kernel_options.py
index b51b0be1d..84e9c145d 100755
--- a/smoketest/scripts/system/test_kernel_options.py
+++ b/smoketest/scripts/system/test_kernel_options.py
@@ -134,5 +134,14 @@ class TestKernelModules(unittest.TestCase):
tmp = re.findall(f'{option}=y', self._config_data)
self.assertTrue(tmp)
+ def test_amd_pstate(self):
+ # AMD pstate driver required as we have "set system option kernel amd-pstate-driver"
+ for option in ['CONFIG_X86_AMD_PSTATE']:
+ tmp = re.findall(f'{option}=y', self._config_data)
+ self.assertTrue(tmp)
+ for option in ['CONFIG_X86_AMD_PSTATE_DEFAULT_MODE']:
+ tmp = re.findall(f'{option}=3', self._config_data)
+ self.assertTrue(tmp)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index cebe57092..274ca2ce6 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -205,7 +205,7 @@ def verify_rule(firewall, family, hook, priority, rule_id, rule_conf):
if 'jump' not in rule_conf['action']:
raise ConfigError('jump-target defined, but action jump needed and it is not defined')
target = rule_conf['jump_target']
- if hook != 'name': # This is a bit clumsy, but consolidates a chunk of code.
+ if hook != 'name': # This is a bit clumsy, but consolidates a chunk of code.
verify_jump_target(firewall, hook, target, family, recursive=True)
else:
verify_jump_target(firewall, hook, target, family, recursive=False)
@@ -268,12 +268,12 @@ def verify_rule(firewall, family, hook, priority, rule_id, rule_conf):
if dict_search_args(rule_conf, 'gre', 'flags', 'checksum') is None:
# There is no builtin match in nftables for the GRE key, so we need to do a raw lookup.
- # The offset of the key within the packet shifts depending on the C-flag.
- # 99% of the time, nobody will have checksums enabled - it's usually a manual config option.
- # We can either assume it is unset unless otherwise directed
+ # The offset of the key within the packet shifts depending on the C-flag.
+ # 99% of the time, nobody will have checksums enabled - it's usually a manual config option.
+ # We can either assume it is unset unless otherwise directed
# (confusing, requires doco to explain why it doesn't work sometimes)
- # or, demand an explicit selection to be made for this specific match rule.
- # This check enforces the latter. The user is free to create rules for both cases.
+ # or, demand an explicit selection to be made for this specific match rule.
+ # This check enforces the latter. The user is free to create rules for both cases.
raise ConfigError('Matching GRE tunnel key requires an explicit checksum flag match. For most cases, use "gre flags checksum unset"')
if dict_search_args(rule_conf, 'gre', 'flags', 'key', 'unset') is not None:
@@ -286,7 +286,7 @@ def verify_rule(firewall, family, hook, priority, rule_id, rule_conf):
if gre_inner_value < 0 or gre_inner_value > 65535:
raise ConfigError('inner-proto outside valid ethertype range 0-65535')
except ValueError:
- pass # Symbolic constant, pre-validated before reaching here.
+ pass # Symbolic constant, pre-validated before reaching here.
tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
if tcp_flags:
@@ -437,6 +437,16 @@ def verify(firewall):
for ifname in interfaces:
verify_hardware_offload(ifname)
+ if 'offload' in firewall.get('global_options', {}).get('state_policy', {}):
+ offload_path = firewall['global_options']['state_policy']['offload']
+ if 'offload_target' not in offload_path:
+ raise ConfigError('offload-target must be specified')
+
+ offload_target = offload_path['offload_target']
+
+ if not dict_search_args(firewall, 'flowtable', offload_target):
+ raise ConfigError(f'Invalid offload-target. Flowtable "{offload_target}" does not exist on the system')
+
if 'group' in firewall:
for group_type in nested_group_types:
if group_type in firewall['group']:
@@ -627,7 +637,7 @@ def apply(firewall):
# Call helper script to Update set contents
if 'name' in firewall['geoip_updated'] or 'ipv6_name' in firewall['geoip_updated']:
print('Updating GeoIP. Please wait...')
- geoip_update(firewall)
+ geoip_update(firewall=firewall)
return None
diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py
index aff93af2a..95dcc543e 100755
--- a/src/conf_mode/interfaces_bridge.py
+++ b/src/conf_mode/interfaces_bridge.py
@@ -25,6 +25,7 @@ from vyos.configdict import has_vlan_subinterface_configured
from vyos.configverify import verify_dhcpv6
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_mtu_ipv6
from vyos.ifconfig import BridgeIf
from vyos.configdict import has_address_configured
from vyos.configdict import has_vrf_configured
@@ -136,6 +137,7 @@ def verify(bridge):
verify_dhcpv6(bridge)
verify_vrf(bridge)
+ verify_mtu_ipv6(bridge)
verify_mirror_redirect(bridge)
ifname = bridge['ifname']
diff --git a/src/conf_mode/interfaces_pseudo-ethernet.py b/src/conf_mode/interfaces_pseudo-ethernet.py
index 446beffd3..b066fd542 100755
--- a/src/conf_mode/interfaces_pseudo-ethernet.py
+++ b/src/conf_mode/interfaces_pseudo-ethernet.py
@@ -27,6 +27,7 @@ from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_mtu_parent
+from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_mirror_redirect
from vyos.ifconfig import MACVLANIf
from vyos.utils.network import interface_exists
@@ -71,6 +72,7 @@ def verify(peth):
verify_vrf(peth)
verify_address(peth)
verify_mtu_parent(peth, peth['parent'])
+ verify_mtu_ipv6(peth)
verify_mirror_redirect(peth)
# use common function to verify VLAN configuration
verify_vlan_config(peth)
diff --git a/src/conf_mode/interfaces_virtual-ethernet.py b/src/conf_mode/interfaces_virtual-ethernet.py
index cb6104f59..59ce474fc 100755
--- a/src/conf_mode/interfaces_virtual-ethernet.py
+++ b/src/conf_mode/interfaces_virtual-ethernet.py
@@ -23,6 +23,7 @@ from vyos.configdict import get_interface_dict
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_mtu_ipv6
from vyos.ifconfig import VethIf
from vyos.utils.network import interface_exists
airbag.enable()
@@ -62,6 +63,7 @@ def verify(veth):
return None
verify_vrf(veth)
+ verify_mtu_ipv6(veth)
verify_address(veth)
if 'peer_name' not in veth:
diff --git a/src/conf_mode/interfaces_vti.py b/src/conf_mode/interfaces_vti.py
index 20629c6c1..915bde066 100755
--- a/src/conf_mode/interfaces_vti.py
+++ b/src/conf_mode/interfaces_vti.py
@@ -20,6 +20,7 @@ from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_mtu_ipv6
from vyos.ifconfig import VTIIf
from vyos import ConfigError
from vyos import airbag
@@ -40,6 +41,7 @@ def get_config(config=None):
def verify(vti):
verify_vrf(vti)
+ verify_mtu_ipv6(vti)
verify_mirror_redirect(vti)
return None
diff --git a/src/conf_mode/interfaces_wwan.py b/src/conf_mode/interfaces_wwan.py
index 230eb14d6..ddbebfb4a 100755
--- a/src/conf_mode/interfaces_wwan.py
+++ b/src/conf_mode/interfaces_wwan.py
@@ -26,6 +26,7 @@ from vyos.configverify import verify_authentication
from vyos.configverify import verify_interface_exists
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_vrf
+from vyos.configverify import verify_mtu_ipv6
from vyos.ifconfig import WWANIf
from vyos.utils.dict import dict_search
from vyos.utils.process import cmd
@@ -98,6 +99,7 @@ def verify(wwan):
verify_interface_exists(wwan, ifname)
verify_authentication(wwan)
verify_vrf(wwan)
+ verify_mtu_ipv6(wwan)
verify_mirror_redirect(wwan)
return None
diff --git a/src/conf_mode/policy_route.py b/src/conf_mode/policy_route.py
index 223175b8a..521764896 100755
--- a/src/conf_mode/policy_route.py
+++ b/src/conf_mode/policy_route.py
@@ -21,13 +21,16 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
+from vyos.configdiff import get_config_diff, Diff
from vyos.template import render
from vyos.utils.dict import dict_search_args
+from vyos.utils.dict import dict_search_recursive
from vyos.utils.process import cmd
from vyos.utils.process import run
from vyos.utils.network import get_vrf_tableid
from vyos.defaults import rt_global_table
from vyos.defaults import rt_global_vrf
+from vyos.firewall import geoip_update
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -43,6 +46,43 @@ valid_groups = [
'interface_group'
]
+def geoip_updated(conf, policy):
+ diff = get_config_diff(conf)
+ node_diff = diff.get_child_nodes_diff(['policy'], expand_nodes=Diff.DELETE, recursive=True)
+
+ out = {
+ 'name': [],
+ 'ipv6_name': [],
+ 'deleted_name': [],
+ 'deleted_ipv6_name': []
+ }
+ updated = False
+
+ for key, path in dict_search_recursive(policy, 'geoip'):
+ set_name = f'GEOIP_CC_{path[0]}_{path[1]}_{path[3]}'
+ if (path[0] == 'route'):
+ out['name'].append(set_name)
+ elif (path[0] == 'route6'):
+ set_name = f'GEOIP_CC6_{path[0]}_{path[1]}_{path[3]}'
+ out['ipv6_name'].append(set_name)
+
+ updated = True
+
+ if 'delete' in node_diff:
+ for key, path in dict_search_recursive(node_diff['delete'], 'geoip'):
+ set_name = f'GEOIP_CC_{path[0]}_{path[1]}_{path[3]}'
+ if (path[0] == 'route'):
+ out['deleted_name'].append(set_name)
+ elif (path[0] == 'route6'):
+ set_name = f'GEOIP_CC6_{path[0]}_{path[1]}_{path[3]}'
+ out['deleted_ipv6_name'].append(set_name)
+ updated = True
+
+ if updated:
+ return out
+
+ return False
+
def get_config(config=None):
if config:
conf = config
@@ -60,6 +100,7 @@ def get_config(config=None):
if 'dynamic_group' in policy['firewall_group']:
del policy['firewall_group']['dynamic_group']
+ policy['geoip_updated'] = geoip_updated(conf, policy)
return policy
def verify_rule(policy, name, rule_conf, ipv6, rule_id):
@@ -203,6 +244,12 @@ def apply(policy):
apply_table_marks(policy)
+ if policy['geoip_updated']:
+ # Call helper script to Update set contents
+ if 'name' in policy['geoip_updated'] or 'ipv6_name' in policy['geoip_updated']:
+ print('Updating GeoIP. Please wait...')
+ geoip_update(policy=policy)
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py
index 5a729af74..99c7e6a1f 100755
--- a/src/conf_mode/service_dhcp-server.py
+++ b/src/conf_mode/service_dhcp-server.py
@@ -41,9 +41,9 @@ from vyos import airbag
airbag.enable()
-ctrl_config_file = '/run/kea/kea-ctrl-agent.conf'
ctrl_socket = '/run/kea/dhcp4-ctrl-socket'
config_file = '/run/kea/kea-dhcp4.conf'
+config_file_d2 = '/run/kea/kea-dhcp-ddns.conf'
lease_file = '/config/dhcp/dhcp4-leases.csv'
lease_file_glob = '/config/dhcp/dhcp4-leases*'
user_group = '_kea'
@@ -171,6 +171,15 @@ def get_config(config=None):
return dhcp
+def verify_ddns_domain_servers(domain_type, domain):
+ if 'dns_server' in domain:
+ invalid_servers = []
+ for server_no, server_config in domain['dns_server'].items():
+ if 'address' not in server_config:
+ invalid_servers.append(server_no)
+ if len(invalid_servers) > 0:
+ raise ConfigError(f'{domain_type} DNS servers {", ".join(invalid_servers)} in DDNS configuration need to have an IP address')
+ return None
def verify(dhcp):
# bail out early - looks like removal from running config
@@ -423,6 +432,22 @@ def verify(dhcp):
if not interface_exists(interface):
raise ConfigError(f'listen-interface "{interface}" does not exist')
+ if 'dynamic_dns_update' in dhcp:
+ ddns = dhcp['dynamic_dns_update']
+ if 'tsig_key' in ddns:
+ invalid_keys = []
+ for tsig_key_name, tsig_key_config in ddns['tsig_key'].items():
+ if not ('algorithm' in tsig_key_config and 'secret' in tsig_key_config):
+ invalid_keys.append(tsig_key_name)
+ if len(invalid_keys) > 0:
+ raise ConfigError(f'Both algorithm and secret need to be set for TSIG keys: {", ".join(invalid_keys)}')
+
+ if 'forward_domain' in ddns:
+ verify_ddns_domain_servers('Forward', ddns['forward_domain'])
+
+ if 'reverse_domain' in ddns:
+ verify_ddns_domain_servers('Reverse', ddns['reverse_domain'])
+
return None
@@ -480,25 +505,26 @@ def generate(dhcp):
dhcp['high_availability']['ca_cert_file'] = ca_cert_file
render(
- ctrl_config_file,
- 'dhcp-server/kea-ctrl-agent.conf.j2',
- dhcp,
- user=user_group,
- group=user_group,
- )
- render(
config_file,
'dhcp-server/kea-dhcp4.conf.j2',
dhcp,
user=user_group,
group=user_group,
)
+ if 'dynamic_dns_update' in dhcp:
+ render(
+ config_file_d2,
+ 'dhcp-server/kea-dhcp-ddns.conf.j2',
+ dhcp,
+ user=user_group,
+ group=user_group
+ )
return None
def apply(dhcp):
- services = ['kea-ctrl-agent', 'kea-dhcp4-server', 'kea-dhcp-ddns-server']
+ services = ['kea-dhcp4-server', 'kea-dhcp-ddns-server']
if not dhcp or 'disable' in dhcp:
for service in services:
@@ -515,9 +541,6 @@ def apply(dhcp):
if service == 'kea-dhcp-ddns-server' and 'dynamic_dns_update' not in dhcp:
action = 'stop'
- if service == 'kea-ctrl-agent' and 'high_availability' not in dhcp:
- action = 'stop'
-
call(f'systemctl {action} {service}.service')
return None
diff --git a/src/conf_mode/service_https.py b/src/conf_mode/service_https.py
index 9e58b4c72..2123823f4 100755
--- a/src/conf_mode/service_https.py
+++ b/src/conf_mode/service_https.py
@@ -28,6 +28,7 @@ from vyos.configverify import verify_vrf
from vyos.configverify import verify_pki_certificate
from vyos.configverify import verify_pki_ca_certificate
from vyos.configverify import verify_pki_dh_parameters
+from vyos.configdiff import get_config_diff
from vyos.defaults import api_config_state
from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
@@ -79,6 +80,14 @@ def get_config(config=None):
# merge CLI and default dictionary
https = config_dict_merge(default_values, https)
+
+ # some settings affecting nginx will require a restart:
+ # for example, a reload will not suffice when binding the listen address
+ # after nginx has started and dropped privileges; add flag here
+ diff = get_config_diff(conf)
+ children_changed = diff.node_changed_children(base)
+ https['nginx_restart_required'] = bool(set(children_changed) != set(['api']))
+
return https
def verify(https):
@@ -208,7 +217,10 @@ def apply(https):
elif is_systemd_service_active(http_api_service_name):
call(f'systemctl stop {http_api_service_name}')
- call(f'systemctl reload-or-restart {https_service_name}')
+ if https['nginx_restart_required']:
+ call(f'systemctl restart {https_service_name}')
+ else:
+ call(f'systemctl reload-or-restart {https_service_name}')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/service_ids_ddos-protection.py b/src/conf_mode/service_ids_ddos-protection.py
deleted file mode 100755
index 276a71fcb..000000000
--- a/src/conf_mode/service_ids_ddos-protection.py
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018-2023 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.template import render
-from vyos.utils.process import call
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-config_file = r'/run/fastnetmon/fastnetmon.conf'
-networks_list = r'/run/fastnetmon/networks_list'
-excluded_networks_list = r'/run/fastnetmon/excluded_networks_list'
-attack_dir = '/var/log/fastnetmon_attacks'
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
- base = ['service', 'ids', 'ddos-protection']
- if not conf.exists(base):
- return None
-
- fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- with_recursive_defaults=True)
-
- return fastnetmon
-
-def verify(fastnetmon):
- if not fastnetmon:
- return None
-
- if 'mode' not in fastnetmon:
- raise ConfigError('Specify operating mode!')
-
- if fastnetmon.get('mode') == 'mirror' and 'listen_interface' not in fastnetmon:
- raise ConfigError("Incorrect settings for 'mode mirror': must specify interface(s) for traffic mirroring")
-
- if fastnetmon.get('mode') == 'sflow' and 'listen_address' not in fastnetmon.get('sflow', {}):
- raise ConfigError("Incorrect settings for 'mode sflow': must specify sFlow 'listen-address'")
-
- if 'alert_script' in fastnetmon:
- if os.path.isfile(fastnetmon['alert_script']):
- # Check script permissions
- if not os.access(fastnetmon['alert_script'], os.X_OK):
- raise ConfigError('Script "{alert_script}" is not executable!'.format(fastnetmon['alert_script']))
- else:
- raise ConfigError('File "{alert_script}" does not exists!'.format(fastnetmon))
-
-def generate(fastnetmon):
- if not fastnetmon:
- for file in [config_file, networks_list]:
- if os.path.isfile(file):
- os.unlink(file)
-
- return None
-
- # Create dir for log attack details
- if not os.path.exists(attack_dir):
- os.mkdir(attack_dir)
-
- render(config_file, 'ids/fastnetmon.j2', fastnetmon)
- render(networks_list, 'ids/fastnetmon_networks_list.j2', fastnetmon)
- render(excluded_networks_list, 'ids/fastnetmon_excluded_networks_list.j2', fastnetmon)
- return None
-
-def apply(fastnetmon):
- systemd_service = 'fastnetmon.service'
- if not fastnetmon:
- # Stop fastnetmon service if removed
- call(f'systemctl stop {systemd_service}')
- else:
- call(f'systemctl reload-or-restart {systemd_service}')
-
- 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/system_host-name.py b/src/conf_mode/system_host-name.py
index fef034d1c..de4accda2 100755
--- a/src/conf_mode/system_host-name.py
+++ b/src/conf_mode/system_host-name.py
@@ -175,7 +175,7 @@ def apply(config):
# Restart services that use the hostname
if hostname_new != hostname_old:
- tmp = systemd_services['rsyslog']
+ tmp = systemd_services['syslog']
call(f'systemctl restart {tmp}')
# If SNMP is running, restart it too
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index 064a1aa91..3d76a1eaa 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2024 VyOS maintainers and contributors
+# Copyright (C) 2019-2025 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
@@ -122,6 +122,10 @@ def generate(options):
render(ssh_config, 'system/ssh_config.j2', options)
render(usb_autosuspend, 'system/40_usb_autosuspend.j2', options)
+ # XXX: This code path and if statements must be kept in sync with the Kernel
+ # option handling in image_installer.py:get_cli_kernel_options(). This
+ # occurance is used for having the appropriate options passed to GRUB
+ # when re-configuring options on the CLI.
cmdline_options = []
if 'kernel' in options:
if 'disable_mitigations' in options['kernel']:
@@ -131,8 +135,9 @@ def generate(options):
if 'amd_pstate_driver' in options['kernel']:
mode = options['kernel']['amd_pstate_driver']
cmdline_options.append(
- f'initcall_blacklist=acpi_cpufreq_init amd_pstate={mode}'
- )
+ f'initcall_blacklist=acpi_cpufreq_init amd_pstate={mode}')
+ if 'quiet' in options['kernel']:
+ cmdline_options.append('quiet')
grub_util.update_kernel_cmdline_options(' '.join(cmdline_options))
return None
diff --git a/src/conf_mode/system_syslog.py b/src/conf_mode/system_syslog.py
index 414bd4b6b..bdab09f3c 100755
--- a/src/conf_mode/system_syslog.py
+++ b/src/conf_mode/system_syslog.py
@@ -35,7 +35,7 @@ rsyslog_conf = '/run/rsyslog/rsyslog.conf'
logrotate_conf = '/etc/logrotate.d/vyos-rsyslog'
systemd_socket = 'syslog.socket'
-systemd_service = systemd_services['rsyslog']
+systemd_service = systemd_services['syslog']
def get_config(config=None):
if config:
diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/06-vyos-nodefaultroute b/src/etc/dhcp/dhclient-enter-hooks.d/06-vyos-nodefaultroute
new file mode 100644
index 000000000..38f674276
--- /dev/null
+++ b/src/etc/dhcp/dhclient-enter-hooks.d/06-vyos-nodefaultroute
@@ -0,0 +1,20 @@
+# Don't add default route if no-default-route is configured for interface
+
+# As configuration is not available to cli-shell-api at the first boot, we must use vyos.config, which contains a workaround for this
+function get_no_default_route {
+python3 - <<PYEND
+from vyos.config import Config
+import os
+
+config = Config()
+if config.exists('interfaces'):
+ iface_types = config.list_nodes('interfaces')
+ for iface_type in iface_types:
+ if config.exists("interfaces {} {} dhcp-options no-default-route".format(iface_type, os.environ['interface'])):
+ print("True")
+PYEND
+}
+
+if [[ "$(get_no_default_route)" == 'True' ]]; then
+ new_routers=""
+fi
diff --git a/src/etc/netplug/vyos-netplug-dhcp-client b/src/etc/netplug/vyos-netplug-dhcp-client
index 7fe6cda75..a230fe900 100755
--- a/src/etc/netplug/vyos-netplug-dhcp-client
+++ b/src/etc/netplug/vyos-netplug-dhcp-client
@@ -20,10 +20,10 @@ import sys
from time import sleep
from vyos.config import Config
-from vyos.configdict import get_interface_dict
-from vyos.ifconfig import Interface
from vyos.ifconfig import Section
from vyos.utils.boot import boot_configuration_complete
+from vyos.utils.process import cmd
+from vyos.utils.process import is_systemd_service_active
from vyos.utils.commit import commit_in_progress
from vyos import airbag
@@ -38,21 +38,34 @@ if not boot_configuration_complete():
sys.exit(1)
interface = sys.argv[1]
-# helper scripts should only work on physical interfaces not on individual
-# sub-interfaces. Moving e.g. a VLAN interface in/out a VRF will also trigger
-# this script which should be prohibited - bail out early
-if '.' in interface:
- sys.exit(0)
while commit_in_progress():
- sleep(1)
+ sleep(0.250)
in_out = sys.argv[2]
config = Config()
interface_path = ['interfaces'] + Section.get_config_path(interface).split()
-_, interface_config = get_interface_dict(
- config, interface_path[:-1], ifname=interface, with_pki=True
-)
-if 'deleted' not in interface_config:
- Interface(interface).update(interface_config)
+
+systemdV4_service = f'dhclient@{interface}.service'
+systemdV6_service = f'dhcp6c@{interface}.service'
+if in_out == 'out':
+ # Interface moved state to down
+ if is_systemd_service_active(systemdV4_service):
+ cmd(f'systemctl stop {systemdV4_service}')
+ if is_systemd_service_active(systemdV6_service):
+ cmd(f'systemctl stop {systemdV6_service}')
+elif in_out == 'in':
+ if config.exists_effective(interface_path + ['address']):
+ tmp = config.return_effective_values(interface_path + ['address'])
+ # Always (re-)start the DHCP(v6) client service. If the DHCP(v6) client
+ # is already running - which could happen if the interface is re-
+ # configured in operational down state, it will have a backoff
+ # time increasing while not receiving a DHCP(v6) reply.
+ #
+ # To make the interface instantly available, and as for a DHCP(v6) lease
+ # we will re-start the service and thus cancel the backoff time.
+ if 'dhcp' in tmp:
+ cmd(f'systemctl restart {systemdV4_service}')
+ if 'dhcpv6' in tmp:
+ cmd(f'systemctl restart {systemdV6_service}')
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index 76be41ddc..ef81cebac 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -83,6 +83,16 @@ net.ipv4.conf.default.ignore_routes_with_linkdown=1
net.ipv6.conf.all.ignore_routes_with_linkdown=1
net.ipv6.conf.default.ignore_routes_with_linkdown=1
+# Disable IPv6 interface autoconfigurationnable packet forwarding for IPv6
+net.ipv6.conf.all.autoconf=0
+net.ipv6.conf.default.autoconf=0
+net.ipv6.conf.*.autoconf=0
+
+# Disable IPv6 router advertisements
+net.ipv6.conf.all.accept_ra=0
+net.ipv6.conf.default.accept_ra=0
+net.ipv6.conf.*.accept_ra=0
+
# Enable packet forwarding for IPv6
net.ipv6.conf.all.forwarding=1
diff --git a/src/etc/systemd/system/fastnetmon.service.d/override.conf b/src/etc/systemd/system/fastnetmon.service.d/override.conf
deleted file mode 100644
index 841666070..000000000
--- a/src/etc/systemd/system/fastnetmon.service.d/override.conf
+++ /dev/null
@@ -1,12 +0,0 @@
-[Unit]
-RequiresMountsFor=/run
-ConditionPathExists=/run/fastnetmon/fastnetmon.conf
-After=
-After=vyos-router.service
-
-[Service]
-Type=simple
-WorkingDirectory=/run/fastnetmon
-PIDFile=/run/fastnetmon.pid
-ExecStart=
-ExecStart=/usr/sbin/fastnetmon --configuration_file /run/fastnetmon/fastnetmon.conf
diff --git a/src/etc/systemd/system/kea-ctrl-agent.service.d/override.conf b/src/etc/systemd/system/kea-ctrl-agent.service.d/override.conf
deleted file mode 100644
index c74fafb42..000000000
--- a/src/etc/systemd/system/kea-ctrl-agent.service.d/override.conf
+++ /dev/null
@@ -1,10 +0,0 @@
-[Unit]
-After=
-After=vyos-router.service
-ConditionFileNotEmpty=
-
-[Service]
-ExecStart=
-ExecStart=/usr/sbin/kea-ctrl-agent -c /run/kea/kea-ctrl-agent.conf
-AmbientCapabilities=CAP_NET_BIND_SERVICE
-CapabilityBoundingSet=CAP_NET_BIND_SERVICE
diff --git a/src/etc/systemd/system/kea-dhcp-ddns-server.service.d/override.conf b/src/etc/systemd/system/kea-dhcp-ddns-server.service.d/override.conf
new file mode 100644
index 000000000..cdfdea8eb
--- /dev/null
+++ b/src/etc/systemd/system/kea-dhcp-ddns-server.service.d/override.conf
@@ -0,0 +1,7 @@
+[Unit]
+After=
+After=vyos-router.service
+
+[Service]
+ExecStart=
+ExecStart=/usr/sbin/kea-dhcp-ddns -c /run/kea/kea-dhcp-ddns.conf
diff --git a/src/helpers/geoip-update.py b/src/helpers/geoip-update.py
index 34accf2cc..061c95401 100755
--- a/src/helpers/geoip-update.py
+++ b/src/helpers/geoip-update.py
@@ -25,20 +25,19 @@ def get_config(config=None):
conf = config
else:
conf = ConfigTreeQuery()
- base = ['firewall']
- if not conf.exists(base):
- return None
-
- return conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
+ return (
+ conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True) if conf.exists(['firewall']) else None,
+ conf.get_config_dict(['policy'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True) if conf.exists(['policy']) else None,
+ )
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("--force", help="Force update", action="store_true")
args = parser.parse_args()
- firewall = get_config()
-
- if not geoip_update(firewall, force=args.force):
+ firewall, policy = get_config()
+ if not geoip_update(firewall=firewall, policy=policy, force=args.force):
sys.exit(1)
diff --git a/src/init/vyos-router b/src/init/vyos-router
index ab3cc42cb..8584234b3 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -417,6 +417,7 @@ gen_duid ()
start ()
{
+ echo -e "Initializing VyOS router\033[0m"
# reset and clean config files
security_reset || log_failure_msg "security reset failed"
@@ -459,6 +460,14 @@ start ()
nfct helper add tns inet6 tcp
nft --file /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules"
+ # Ensure rsyslog is the default syslog daemon
+ SYSTEMD_SYSLOG="/etc/systemd/system/syslog.service"
+ SYSTEMD_RSYSLOG="/lib/systemd/system/rsyslog.service"
+ if [ ! -L ${SYSTEMD_SYSLOG} ] || [ "$(readlink -f ${SYSTEMD_SYSLOG})" != "${SYSTEMD_RSYSLOG}" ]; then
+ ln -sf ${SYSTEMD_RSYSLOG} ${SYSTEMD_SYSLOG}
+ systemctl daemon-reload
+ fi
+
# As VyOS does not execute commands that are not present in the CLI we call
# the script by hand to have a single source for the login banner and MOTD
${vyos_conf_scripts_dir}/system_syslog.py || log_failure_msg "could not reset syslog"
@@ -517,7 +526,6 @@ start ()
cleanup_post_commit_hooks
- log_daemon_msg "Starting VyOS router"
disabled migrate || migrate_bootfile
restore_if_missing_preconfig_script
@@ -557,6 +565,9 @@ start ()
if [[ ! -z "$tmp" ]]; then
vtysh -c "rpki start"
fi
+
+ # Start netplug daemon
+ systemctl start netplug.service
}
stop()
@@ -574,8 +585,8 @@ stop()
umount ${vyatta_configdir}
log_action_end_msg $?
+ systemctl stop netplug.service
systemctl stop vyconfd.service
-
systemctl stop frr.service
unmount_encrypted_config
diff --git a/src/migration-scripts/dhcp-server/7-to-8 b/src/migration-scripts/dhcp-server/7-to-8
index 7fcb62e86..d0f9455bb 100644
--- a/src/migration-scripts/dhcp-server/7-to-8
+++ b/src/migration-scripts/dhcp-server/7-to-8
@@ -41,9 +41,6 @@ def migrate(config: ConfigTree) -> None:
for network in config.list_nodes(base + ['shared-network-name']):
base_network = base + ['shared-network-name', network]
- if config.exists(base_network + ['ping-check']):
- config.delete(base_network + ['ping-check'])
-
if config.exists(base_network + ['shared-network-parameters']):
config.delete(base_network +['shared-network-parameters'])
@@ -57,9 +54,6 @@ def migrate(config: ConfigTree) -> None:
if config.exists(base_subnet + ['enable-failover']):
config.delete(base_subnet + ['enable-failover'])
- if config.exists(base_subnet + ['ping-check']):
- config.delete(base_subnet + ['ping-check'])
-
if config.exists(base_subnet + ['subnet-parameters']):
config.delete(base_subnet + ['subnet-parameters'])
diff --git a/src/migration-scripts/ids/1-to-2 b/src/migration-scripts/ids/1-to-2
new file mode 100644
index 000000000..4c0333c88
--- /dev/null
+++ b/src/migration-scripts/ids/1-to-2
@@ -0,0 +1,30 @@
+# Copyright 2025 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/>.
+
+# T: Migrate threshold and add new threshold types
+
+from vyos.configtree import ConfigTree
+
+# The old 'service ids' path was only used for FastNetMon
+# Suricata is in 'service suricata',
+# so this isn't an overreach
+base = ['service', 'ids']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(base):
+ # Nothing to do
+ return
+ else:
+ config.delete(base)
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
index 086536e4e..ac47e3273 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -598,6 +598,9 @@ def show_firewall_group(name=None):
prefix = 'DA_' if dynamic_type == 'address_group' else 'DA6_'
if dynamic_type in firewall['group']['dynamic_group']:
for dynamic_name, dynamic_conf in firewall['group']['dynamic_group'][dynamic_type].items():
+ if name and name != dynamic_name:
+ continue
+
references = find_references(dynamic_type, dynamic_name)
row = [dynamic_name, textwrap.fill(dynamic_conf.get('description') or '', 50), dynamic_type + '(dynamic)', '\n'.join(references) or 'N/D']
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index 179913f15..3af2232bb 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -24,7 +24,9 @@ from glob import glob
from sys import exit
from os import environ
from os import readlink
-from os import getpid, getppid
+from os import getpid
+from os import getppid
+from json import loads
from typing import Union
from urllib.parse import urlparse
from passlib.hosts import linux_context
@@ -35,15 +37,23 @@ from psutil import disk_partitions
from vyos.base import Warning
from vyos.configtree import ConfigTree
from vyos.remote import download
-from vyos.system import disk, grub, image, compat, raid, SYSTEM_CFG_VER
+from vyos.system import disk
+from vyos.system import grub
+from vyos.system import image
+from vyos.system import compat
+from vyos.system import raid
+from vyos.system import SYSTEM_CFG_VER
+from vyos.system import grub_util
from vyos.template import render
from vyos.utils.auth import (
DEFAULT_PASSWORD,
EPasswdStrength,
evaluate_strength
)
+from vyos.utils.dict import dict_search
from vyos.utils.io import ask_input, ask_yes_no, select_entry
from vyos.utils.file import chmod_2775
+from vyos.utils.file import read_file
from vyos.utils.process import cmd, run, rc_cmd
from vyos.version import get_version_data
@@ -477,6 +487,29 @@ def setup_grub(root_dir: str) -> None:
render(grub_cfg_menu, grub.TMPL_GRUB_MENU, {})
render(grub_cfg_options, grub.TMPL_GRUB_OPTS, {})
+def get_cli_kernel_options(config_file: str) -> list:
+ config = ConfigTree(read_file(config_file))
+ config_dict = loads(config.to_json())
+ kernel_options = dict_search('system.option.kernel', config_dict)
+ if kernel_options is None:
+ kernel_options = {}
+ cmdline_options = []
+
+ # XXX: This code path and if statements must be kept in sync with the Kernel
+ # option handling in system_options.py:generate(). This occurance is used
+ # for having the appropriate options passed to GRUB after an image upgrade!
+ if 'disable-mitigations' in kernel_options:
+ cmdline_options.append('mitigations=off')
+ if 'disable-power-saving' in kernel_options:
+ cmdline_options.append('intel_idle.max_cstate=0 processor.max_cstate=1')
+ if 'amd-pstate-driver' in kernel_options:
+ mode = kernel_options['amd-pstate-driver']
+ cmdline_options.append(
+ f'initcall_blacklist=acpi_cpufreq_init amd_pstate={mode}')
+ if 'quiet' in kernel_options:
+ cmdline_options.append('quiet')
+
+ return cmdline_options
def configure_authentication(config_file: str, password: str) -> None:
"""Write encrypted password to config file
@@ -491,10 +524,7 @@ def configure_authentication(config_file: str, password: str) -> None:
plaintext exposed
"""
encrypted_password = linux_context.hash(password)
-
- with open(config_file) as f:
- config_string = f.read()
-
+ config_string = read_file(config_file)
config = ConfigTree(config_string)
config.set([
'system', 'login', 'user', 'vyos', 'authentication',
@@ -1045,6 +1075,12 @@ def add_image(image_path: str, vrf: str = None, username: str = '',
if set_as_default:
grub.set_default(image_name, root_dir)
+ cmdline_options = get_cli_kernel_options(
+ f'{target_config_dir}/config.boot')
+ grub_util.update_kernel_cmdline_options(' '.join(cmdline_options),
+ root_dir=root_dir,
+ version=image_name)
+
except OSError as e:
# if no space error, remove image dir and cleanup
if e.errno == ENOSPC:
diff --git a/src/op_mode/tech_support.py b/src/op_mode/tech_support.py
index 24ac0af1b..c4496dfa3 100644
--- a/src/op_mode/tech_support.py
+++ b/src/op_mode/tech_support.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2024 VyOS maintainers and contributors
+# Copyright (C) 2025 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
@@ -20,6 +20,7 @@ import json
import vyos.opmode
from vyos.utils.process import cmd
+from vyos.base import Warning
def _get_version_data():
from vyos.version import get_version_data
@@ -51,7 +52,12 @@ def _get_storage():
def _get_devices():
devices = {}
devices["pci"] = cmd("lspci")
- devices["usb"] = cmd("lsusb")
+
+ try:
+ devices["usb"] = cmd("lsusb")
+ except OSError:
+ Warning("Could not retrieve information about USB devices")
+ devices["usb"] = {}
return devices
diff --git a/src/services/vyos-domain-resolver b/src/services/vyos-domain-resolver
index aba5ba9db..4419fc4a7 100755
--- a/src/services/vyos-domain-resolver
+++ b/src/services/vyos-domain-resolver
@@ -92,12 +92,14 @@ def resolve(domains, ipv6=False):
for domain in domains:
resolved = fqdn_resolve(domain, ipv6=ipv6)
+ cache_key = f'{domain}_ipv6' if ipv6 else domain
+
if resolved and cache:
- domain_state[domain] = resolved
+ domain_state[cache_key] = resolved
elif not resolved:
- if domain not in domain_state:
+ if cache_key not in domain_state:
continue
- resolved = domain_state[domain]
+ resolved = domain_state[cache_key]
ip_list = ip_list | resolved
return ip_list
diff --git a/src/systemd/netplug.service b/src/systemd/netplug.service
new file mode 100644
index 000000000..928c553e8
--- /dev/null
+++ b/src/systemd/netplug.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Network cable hotplug management daemon
+Documentation=man:netplugd(8)
+After=vyos-router.service
+
+[Service]
+Type=forking
+PIDFile=/run/netplugd.pid
+ExecStart=/sbin/netplugd -c /etc/netplug/netplugd.conf -p /run/netplugd.pid
diff --git a/src/systemd/vyos.target b/src/systemd/vyos.target
index 47c91c1cc..ea1593fe9 100644
--- a/src/systemd/vyos.target
+++ b/src/systemd/vyos.target
@@ -1,3 +1,3 @@
[Unit]
Description=VyOS target
-After=multi-user.target
+After=multi-user.target vyos-grub-update.service systemd-sysctl.service