summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes79
-rw-r--r--.github/workflows/stale.yml22
-rw-r--r--.vscode/settings.json28
-rw-r--r--data/configd-include.json1
-rw-r--r--data/templates/container/containers.conf.j21418
-rw-r--r--data/templates/dhcp-server/10-override.conf.j230
-rw-r--r--data/templates/dhcp-server/dhcpd.conf.j2250
-rw-r--r--data/templates/dhcp-server/dhcpdv6.conf.j2132
-rw-r--r--data/templates/dhcp-server/kea-ctrl-agent.conf.j214
-rw-r--r--data/templates/dhcp-server/kea-dhcp4.conf.j272
-rw-r--r--data/templates/dhcp-server/kea-dhcp6.conf.j248
-rw-r--r--data/templates/frr/bgpd.frr.j28
-rw-r--r--data/templates/frr/eigrpd.frr.j240
-rw-r--r--data/templates/frr/zebra.segment_routing.frr.j223
-rw-r--r--data/templates/system/sysctl.conf.j214
-rw-r--r--data/templates/telegraf/telegraf.j22
-rw-r--r--debian/control5
-rw-r--r--debian/vyos-1x.postinst8
-rw-r--r--interface-definitions/dhcp-server.xml.in52
-rw-r--r--interface-definitions/dhcpv6-server.xml.in52
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i60
-rw-r--r--interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i2
-rw-r--r--interface-definitions/include/constraint/dhcp-client-string-option.xml.i2
-rw-r--r--interface-definitions/include/constraint/host-name.xml.i6
-rw-r--r--interface-definitions/include/dhcp/captive-portal.xml.i11
-rw-r--r--interface-definitions/include/dhcp/ping-check.xml.i8
-rw-r--r--interface-definitions/include/policy/route-common.xml.i514
-rw-r--r--interface-definitions/include/protocol-tcp-udp.xml.i44
-rw-r--r--interface-definitions/include/rip/version.xml.i36
-rw-r--r--interface-definitions/include/version/dhcp-server-version.xml.i2
-rw-r--r--interface-definitions/include/version/dhcpv6-server-version.xml.i2
-rw-r--r--interface-definitions/protocols-segment-routing.xml.in89
-rw-r--r--op-mode-definitions/dhcp.xml.in4
-rwxr-xr-xop-mode-definitions/generate-system-login-user.xml.in180
-rw-r--r--op-mode-definitions/include/ospf/graceful-restart.xml.i2
-rw-r--r--op-mode-definitions/monitor-log.xml.in4
-rw-r--r--op-mode-definitions/show-bgp.xml.in13
-rw-r--r--op-mode-definitions/show-log.xml.in4
-rw-r--r--op-mode-definitions/show-segment-routing.xml.in27
-rw-r--r--python/vyos/config_mgmt.py18
-rw-r--r--python/vyos/configtree.py3
-rw-r--r--python/vyos/kea.py319
-rw-r--r--python/vyos/load_config.py200
-rw-r--r--python/vyos/remote.py2
-rw-r--r--python/vyos/system/disk.py5
-rw-r--r--python/vyos/system/grub.py2
-rw-r--r--python/vyos/system/raid.py31
-rw-r--r--python/vyos/template.py100
-rw-r--r--python/vyos/utils/file.py8
-rw-r--r--python/vyos/utils/io.py9
-rw-r--r--python/vyos/utils/network.py35
-rw-r--r--smoketest/config-tests/dialup-router-medium-vpn5
-rw-r--r--smoketest/configs/basic-vyos18
-rw-r--r--smoketest/configs/dialup-router-wireguard-ipv61629
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py15
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_segment_routing.py70
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-server.py455
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcpv6-server.py156
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_dynamic.py41
-rwxr-xr-xsmoketest/scripts/cli/test_service_https.py69
-rwxr-xr-xsrc/conf_mode/dhcp_server.py134
-rwxr-xr-xsrc/conf_mode/dhcpv6_server.py42
-rwxr-xr-xsrc/conf_mode/dns_dynamic.py26
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py1
-rwxr-xr-xsrc/conf_mode/protocols_segment_routing.py74
-rwxr-xr-xsrc/conf_mode/system-login.py9
-rw-r--r--src/etc/systemd/system/kea-ctrl-agent.service.d/override.conf9
-rw-r--r--src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf7
-rw-r--r--src/etc/systemd/system/kea-dhcp6-server.service.d/override.conf7
-rwxr-xr-xsrc/helpers/simple-download.py20
-rwxr-xr-xsrc/init/vyos-router2
-rwxr-xr-xsrc/migration-scripts/dhcp-server/6-to-787
-rwxr-xr-xsrc/migration-scripts/dhcpv6-server/1-to-286
-rwxr-xr-xsrc/migration-scripts/interfaces/22-to-23144
-rwxr-xr-xsrc/migration-scripts/interfaces/23-to-24143
-rwxr-xr-xsrc/migration-scripts/interfaces/24-to-25399
-rwxr-xr-xsrc/migration-scripts/interfaces/25-to-26377
-rwxr-xr-xsrc/migration-scripts/interfaces/26-to-2727
-rwxr-xr-xsrc/migration-scripts/interfaces/27-to-2825
-rwxr-xr-xsrc/migration-scripts/interfaces/28-to-2925
-rwxr-xr-xsrc/migration-scripts/interfaces/29-to-3061
-rwxr-xr-xsrc/migration-scripts/nat/6-to-78
-rwxr-xr-xsrc/op_mode/clear_dhcp_lease.py41
-rwxr-xr-xsrc/op_mode/dhcp.py116
-rwxr-xr-xsrc/op_mode/image_installer.py58
-rwxr-xr-xsrc/op_mode/nat.py6
-rw-r--r--src/pam-configs/mfa-google-authenticator8
-rwxr-xr-xsrc/system/on-dhcp-event.sh14
-rw-r--r--src/systemd/isc-dhcp-server6.service24
-rwxr-xr-xsrc/validators/bgp-large-community-list8
90 files changed, 5825 insertions, 2661 deletions
diff --git a/.gitattributes b/.gitattributes
index ea2cc59e4..1d79e0804 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,4 +1,75 @@
-data/templates/**/*.conf linguist-language=Jinja
-*.tmpl linguist-language=Jinja
-*.xml.i linguist-language=XML
-*.xml.in linguist-language=XML
+# Common settings that generally should always be used with your language specific settings
+
+# Auto detect text files and perform LF normalization
+* text=auto
+
+#
+# The above will handle all files NOT found below
+#
+
+# Documents
+*.md text diff=markdown
+*.mdx text diff=markdown
+*.adoc text
+*.csv text eol=crlf
+*.rst text
+*.txt text
+
+# Graphics
+*.png binary
+*.jpg binary
+*.jpeg binary
+*.gif binary
+*.tif binary
+*.tiff binary
+*.ico binary
+# SVG treated as text by default.
+*.svg text
+# If you want to treat it as binary,
+# use the following line instead.
+# *.svg binary
+
+# Scripts
+*.bash text eol=lf diff=bash
+*.sh text eol=lf diff=bash
+
+# Serialisation
+*.conf text
+*.graphql text
+*.j2 text
+*.json text
+*.rules text
+*.service text
+*.toml text
+*.tmpl text linguist-language=Jinja
+*.xml text
+*.xml.i text linguist-language=XML
+*.xml.in text linguist-language=XML
+*.yaml text
+*.yml text
+
+# Text files where line endings should be preserved
+*.patch -text
+*.diff -text
+
+# Python files
+*.pxd text diff=python
+*.py text diff=python
+*.py3 text diff=python
+*.pyw text diff=python
+*.pyx text diff=python
+*.pyz text diff=python
+*.pyi text diff=python
+
+# Fix syntax highlighting on GitHub to allow comments
+.vscode/*.json linguist-language=JSON-with-Comments
+
+#
+# Exclude files from exporting
+#
+
+.gitattributes export-ignore
+.gitignore export-ignore
+.gitkeep export-ignore
+*.pyc binary export-ignore
+*.pyo binary export-ignore
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 000000000..d21d151f7
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,22 @@
+name: "Issue and PR stale management"
+on:
+ schedule:
+ - cron: "0 0 * * *"
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ if: github.repository == 'vyos/vyos-1x'
+ steps:
+ # Issue stale management
+ - uses: actions/stale@v6
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ days-before-stale: 90
+ days-before-close: -1
+ stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. The issue will be reviewed by a maintainer and may be closed'
+ stale-issue-label: 'state: stale'
+ exempt-issue-labels: 'state: accepted, state: in-progress'
+ stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. The PR will be reviewed by a maintainer and may be closed'
+ stale-pr-label: 'state: stale'
+ exempt-pr-labels: 'state: accepted, state: in-progress'
diff --git a/.vscode/settings.json b/.vscode/settings.json
index caa87ba4a..27ed4c296 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,18 +1,26 @@
{
- "files.trimTrailingWhitespace": true,
- "editor.tabSize": 4,
- "editor.insertSpaces": true,
"files.insertFinalNewline": true,
- "files.eol": "\n",
- # https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers
+ "files.trimFinalNewlines": true,
+ "files.trimTrailingWhitespace": true,
+
+ // https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers
"files.associations": {
+ "*.j2": "jinja",
"*.xml.i": "xml",
"*.xml.in": "xml",
- "*.j2": "jinja",
},
- "editor.indentSize": "tabSize",
"[jinja]": {
- "editor.tabSize": 4,
- "editor.wordBasedSuggestions": false
- }
+ "editor.wordBasedSuggestions": "off"
+ },
+ // https://code.visualstudio.com/docs/python/settings-reference
+ "python.analysis.extraPaths": [
+ "./python"
+ ],
+ // https://help.gitkraken.com/gitlens/gitlens-settings/#autolink-settings
+ "gitlens.autolinks": [
+ {
+ "prefix": "T",
+ "url": "https://vyos.dev/T<num>"
+ }
+ ],
}
diff --git a/data/configd-include.json b/data/configd-include.json
index a762a6d4c..92d3863ce 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -53,6 +53,7 @@
"protocols_rip.py",
"protocols_ripng.py",
"protocols_rpki.py",
+"protocols_segment_routing.py",
"protocols_static.py",
"protocols_static_multicast.py",
"qos.py",
diff --git a/data/templates/container/containers.conf.j2 b/data/templates/container/containers.conf.j2
index c635ca213..c8b54dfbb 100644
--- a/data/templates/container/containers.conf.j2
+++ b/data/templates/container/containers.conf.j2
@@ -1,709 +1,709 @@
-### Autogenerated by container.py ###
-
-# The containers configuration file specifies all of the available configuration
-# command-line options/flags for container engine tools like Podman & Buildah,
-# but in a TOML format that can be easily modified and versioned.
-
-# Please refer to containers.conf(5) for details of all configuration options.
-# Not all container engines implement all of the options.
-# All of the options have hard coded defaults and these options will override
-# the built in defaults. Users can then override these options via the command
-# line. Container engines will read containers.conf files in up to three
-# locations in the following order:
-# 1. /usr/share/containers/containers.conf
-# 2. /etc/containers/containers.conf
-# 3. $HOME/.config/containers/containers.conf (Rootless containers ONLY)
-# Items specified in the latter containers.conf, if they exist, override the
-# previous containers.conf settings, or the default settings.
-
-[containers]
-
-# List of annotation. Specified as
-# "key = value"
-# If it is empty or commented out, no annotations will be added
-#
-#annotations = []
-
-# Used to change the name of the default AppArmor profile of container engine.
-#
-#apparmor_profile = "container-default"
-
-# The hosts entries from the base hosts file are added to the containers hosts
-# file. This must be either an absolute path or as special values "image" which
-# uses the hosts file from the container image or "none" which means
-# no base hosts file is used. The default is "" which will use /etc/hosts.
-#
-#base_hosts_file = ""
-
-# Default way to to create a cgroup namespace for the container
-# Options are:
-# `private` Create private Cgroup Namespace for the container.
-# `host` Share host Cgroup Namespace with the container.
-#
-#cgroupns = "private"
-
-# Control container cgroup configuration
-# Determines whether the container will create CGroups.
-# Options are:
-# `enabled` Enable cgroup support within container
-# `disabled` Disable cgroup support, will inherit cgroups from parent
-# `no-conmon` Do not create a cgroup dedicated to conmon.
-#
-#cgroups = "enabled"
-
-# List of default capabilities for containers. If it is empty or commented out,
-# the default capabilities defined in the container engine will be added.
-#
-default_capabilities = [
- "CHOWN",
- "DAC_OVERRIDE",
- "FOWNER",
- "FSETID",
- "KILL",
- "NET_BIND_SERVICE",
- "SETFCAP",
- "SETGID",
- "SETPCAP",
- "SETUID",
- "SYS_CHROOT"
-]
-
-# A list of sysctls to be set in containers by default,
-# specified as "name=value",
-# for example:"net.ipv4.ping_group_range=0 0".
-#
-default_sysctls = [
- "net.ipv4.ping_group_range=0 0",
-]
-
-# A list of ulimits to be set in containers by default, specified as
-# "<ulimit name>=<soft limit>:<hard limit>", for example:
-# "nofile=1024:2048"
-# See setrlimit(2) for a list of resource names.
-# Any limit not specified here will be inherited from the process launching the
-# container engine.
-# Ulimits has limits for non privileged container engines.
-#
-#default_ulimits = [
-# "nofile=1280:2560",
-#]
-
-# List of devices. Specified as
-# "<device-on-host>:<device-on-container>:<permissions>", for example:
-# "/dev/sdc:/dev/xvdc:rwm".
-# If it is empty or commented out, only the default devices will be used
-#
-#devices = []
-
-# List of default DNS options to be added to /etc/resolv.conf inside of the container.
-#
-#dns_options = []
-
-# List of default DNS search domains to be added to /etc/resolv.conf inside of the container.
-#
-#dns_searches = []
-
-# Set default DNS servers.
-# This option can be used to override the DNS configuration passed to the
-# container. The special value "none" can be specified to disable creation of
-# /etc/resolv.conf in the container.
-# The /etc/resolv.conf file in the image will be used without changes.
-#
-#dns_servers = []
-
-# Environment variable list for the conmon process; used for passing necessary
-# environment variables to conmon or the runtime.
-#
-#env = [
-# "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
-# "TERM=xterm",
-#]
-
-# Pass all host environment variables into the container.
-#
-#env_host = false
-
-# Set the ip for the host.containers.internal entry in the containers /etc/hosts
-# file. This can be set to "none" to disable adding this entry. By default it
-# will automatically choose the host ip.
-#
-# NOTE: When using podman machine this entry will never be added to the containers
-# hosts file instead the gvproxy dns resolver will resolve this hostname. Therefore
-# it is not possible to disable the entry in this case.
-#
-#host_containers_internal_ip = ""
-
-# Default proxy environment variables passed into the container.
-# The environment variables passed in include:
-# http_proxy, https_proxy, ftp_proxy, no_proxy, and the upper case versions of
-# these. This option is needed when host system uses a proxy but container
-# should not use proxy. Proxy environment variables specified for the container
-# in any other way will override the values passed from the host.
-#
-#http_proxy = true
-
-# Run an init inside the container that forwards signals and reaps processes.
-#
-#init = false
-
-# Container init binary, if init=true, this is the init binary to be used for containers.
-#
-#init_path = "/usr/libexec/podman/catatonit"
-
-# Default way to to create an IPC namespace (POSIX SysV IPC) for the container
-# Options are:
-# "host" Share host IPC Namespace with the container.
-# "none" Create shareable IPC Namespace for the container without a private /dev/shm.
-# "private" Create private IPC Namespace for the container, other containers are not allowed to share it.
-# "shareable" Create shareable IPC Namespace for the container.
-#
-#ipcns = "shareable"
-
-# keyring tells the container engine whether to create
-# a kernel keyring for use within the container.
-#
-#keyring = true
-
-# label tells the container engine whether to use container separation using
-# MAC(SELinux) labeling or not.
-# The label flag is ignored on label disabled systems.
-#
-#label = true
-
-# Logging driver for the container. Available options: k8s-file and journald.
-#
-#log_driver = "k8s-file"
-
-# 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
-# exceed conmon's read buffer. The file is truncated and re-opened so the
-# limit is never exceeded.
-#
-#log_size_max = -1
-
-# Specifies default format tag for container log messages.
-# This is useful for creating a specific tag for container log messages.
-# Containers logs default to truncated container ID as a tag.
-#
-#log_tag = ""
-
-# Default way to to create a Network namespace for the container
-# Options are:
-# `private` Create private Network Namespace for the container.
-# `host` Share host Network Namespace with the container.
-# `none` Containers do not use the network
-#
-#netns = "private"
-
-# Create /etc/hosts for the container. By default, container engine manage
-# /etc/hosts, automatically adding the container's own IP address.
-#
-#no_hosts = false
-
-# Default way to to create a PID namespace for the container
-# Options are:
-# `private` Create private PID Namespace for the container.
-# `host` Share host PID Namespace with the container.
-#
-#pidns = "private"
-
-# Maximum number of processes allowed in a container.
-#
-#pids_limit = 2048
-
-# Copy the content from the underlying image into the newly created volume
-# when the container is created instead of when it is started. If false,
-# the container engine will not copy the content until the container is started.
-# Setting it to true may have negative performance implications.
-#
-#prepare_volume_on_create = false
-
-# Path to the seccomp.json profile which is used as the default seccomp profile
-# for the runtime.
-#
-#seccomp_profile = "/usr/share/containers/seccomp.json"
-
-# Size of /dev/shm. Specified as <number><unit>.
-# Unit is optional, values:
-# b (bytes), k (kilobytes), m (megabytes), or g (gigabytes).
-# If the unit is omitted, the system uses bytes.
-#
-#shm_size = "65536k"
-
-# Set timezone in container. Takes IANA timezones as well as "local",
-# which sets the timezone in the container to match the host machine.
-#
-#tz = ""
-
-# Set umask inside the container
-#
-#umask = "0022"
-
-# Default way to to create a User namespace for the container
-# Options are:
-# `auto` Create unique User Namespace for the container.
-# `host` Share host User Namespace with the container.
-#
-#userns = "host"
-
-# Number of UIDs to allocate for the automatic container creation.
-# UIDs are allocated from the "container" UIDs listed in
-# /etc/subuid & /etc/subgid
-#
-#userns_size = 65536
-
-# Default way to to create a UTS namespace for the container
-# Options are:
-# `private` Create private UTS Namespace for the container.
-# `host` Share host UTS Namespace with the container.
-#
-#utsns = "private"
-
-# List of volumes. Specified as
-# "<directory-on-host>:<directory-in-container>:<options>", for example:
-# "/db:/var/lib/db:ro".
-# If it is empty or commented out, no volumes will be added
-#
-#volumes = []
-
-[secrets]
-#driver = "file"
-
-[secrets.opts]
-#root = "/example/directory"
-
-[network]
-
-# Network backend determines what network driver will be used to set up and tear down container networks.
-# Valid values are "cni" and "netavark".
-# The default value is empty which means that it will automatically choose CNI or netavark. If there are
-# already containers/images or CNI networks preset it will choose CNI.
-#
-# Before changing this value all containers must be stopped otherwise it is likely that
-# iptables rules and network interfaces might leak on the host. A reboot will fix this.
-#
-network_backend = "netavark"
-
-# Path to directory where CNI plugin binaries are located.
-#
-#cni_plugin_dirs = [
-# "/usr/local/libexec/cni",
-# "/usr/libexec/cni",
-# "/usr/local/lib/cni",
-# "/usr/lib/cni",
-# "/opt/cni/bin",
-#]
-
-# The network name of the default network to attach pods to.
-#
-#default_network = "podman"
-
-# The default subnet for the default network given in default_network.
-# If a network with that name does not exist, a new network using that name and
-# this subnet will be created.
-# Must be a valid IPv4 CIDR prefix.
-#
-#default_subnet = "10.88.0.0/16"
-
-# DefaultSubnetPools is a list of subnets and size which are used to
-# allocate subnets automatically for podman network create.
-# It will iterate through the list and will pick the first free subnet
-# with the given size. This is only used for ipv4 subnets, ipv6 subnets
-# are always assigned randomly.
-#
-#default_subnet_pools = [
-# {"base" = "10.89.0.0/16", "size" = 24},
-# {"base" = "10.90.0.0/15", "size" = 24},
-# {"base" = "10.92.0.0/14", "size" = 24},
-# {"base" = "10.96.0.0/11", "size" = 24},
-# {"base" = "10.128.0.0/9", "size" = 24},
-#]
-
-# Path to the directory where network configuration files are located.
-# For the CNI backend the default is "/etc/cni/net.d" as root
-# and "$HOME/.config/cni/net.d" as rootless.
-# For the netavark backend "/etc/containers/networks" is used as root
-# and "$graphroot/networks" as rootless.
-#
-#network_config_dir = "/etc/cni/net.d/"
-
-# Port to use for dns forwarding daemon with netavark in rootful bridge
-# mode and dns enabled.
-# Using an alternate port might be useful if other dns services should
-# run on the machine.
-#
-#dns_bind_port = 53
-
-[engine]
-# Index to the active service
-#
-#active_service = production
-
-# The compression format to use when pushing an image.
-# Valid options are: `gzip`, `zstd` and `zstd:chunked`.
-#
-#compression_format = "gzip"
-
-
-# Cgroup management implementation used for the runtime.
-# Valid options "systemd" or "cgroupfs"
-#
-#cgroup_manager = "systemd"
-
-# Environment variables to pass into conmon
-#
-#conmon_env_vars = [
-# "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
-#]
-
-# Paths to look for the conmon container manager binary
-#
-#conmon_path = [
-# "/usr/libexec/podman/conmon",
-# "/usr/local/libexec/podman/conmon",
-# "/usr/local/lib/podman/conmon",
-# "/usr/bin/conmon",
-# "/usr/sbin/conmon",
-# "/usr/local/bin/conmon",
-# "/usr/local/sbin/conmon"
-#]
-
-# Enforces using docker.io for completing short names in Podman's compatibility
-# REST API. Note that this will ignore unqualified-search-registries and
-# short-name aliases defined in containers-registries.conf(5).
-#compat_api_enforce_docker_hub = true
-
-# Specify the keys sequence used to detach a container.
-# Format is a single character [a-Z] or a comma separated sequence of
-# `ctrl-<value>`, where `<value>` is one of:
-# `a-z`, `@`, `^`, `[`, `\`, `]`, `^` or `_`
-#
-#detach_keys = "ctrl-p,ctrl-q"
-
-# Determines whether engine will reserve ports on the host when they are
-# forwarded to containers. When enabled, when ports are forwarded to containers,
-# ports are held open by as long as the container is running, ensuring that
-# they cannot be reused by other programs on the host. However, this can cause
-# significant memory usage if a container has many ports forwarded to it.
-# Disabling this can save memory.
-#
-#enable_port_reservation = true
-
-# Environment variables to be used when running the container engine (e.g., Podman, Buildah).
-# For example "http_proxy=internal.proxy.company.com".
-# Note these environment variables will not be used within the container.
-# Set the env section under [containers] table, if you want to set environment variables for the container.
-#
-#env = []
-
-# Define where event logs will be stored, when events_logger is "file".
-#events_logfile_path=""
-
-# Sets the maximum size for events_logfile_path.
-# The size can be b (bytes), k (kilobytes), m (megabytes), or g (gigabytes).
-# The format for the size is `<number><unit>`, e.g., `1b` or `3g`.
-# If no unit is included then the size will be read in bytes.
-# When the limit is exceeded, the logfile will be rotated and the old one will be deleted.
-# If the maximum size is set to 0, then no limit will be applied,
-# and the logfile will not be rotated.
-#events_logfile_max_size = "1m"
-
-# Selects which logging mechanism to use for container engine events.
-# Valid values are `journald`, `file` and `none`.
-#
-#events_logger = "journald"
-
-# A is a list of directories which are used to search for helper binaries.
-#
-#helper_binaries_dir = [
-# "/usr/local/libexec/podman",
-# "/usr/local/lib/podman",
-# "/usr/libexec/podman",
-# "/usr/lib/podman",
-#]
-
-# Path to OCI hooks directories for automatically executed hooks.
-#
-#hooks_dir = [
-# "/usr/share/containers/oci/hooks.d",
-#]
-
-# Manifest Type (oci, v2s2, or v2s1) to use when pulling, pushing, building
-# container images. By default image pulled and pushed match the format of the
-# source image. Building/committing defaults to OCI.
-#
-#image_default_format = ""
-
-# Default transport method for pulling and pushing for images
-#
-#image_default_transport = "docker://"
-
-# Maximum number of image layers to be copied (pulled/pushed) simultaneously.
-# Not setting this field, or setting it to zero, will fall back to containers/image defaults.
-#
-#image_parallel_copies = 0
-
-# Tells container engines how to handle the builtin image volumes.
-# * bind: An anonymous named volume will be created and mounted
-# into the container.
-# * tmpfs: The volume is mounted onto the container as a tmpfs,
-# which allows users to create content that disappears when
-# the container is stopped.
-# * ignore: All volumes are just ignored and no action is taken.
-#
-#image_volume_mode = ""
-
-# Default command to run the infra container
-#
-#infra_command = "/pause"
-
-# Infra (pause) container image name for pod infra containers. When running a
-# pod, we start a `pause` process in a container to hold open the namespaces
-# associated with the pod. This container does nothing other then sleep,
-# reserving the pods resources for the lifetime of the pod. By default container
-# engines run a builtin container using the pause executable. If you want override
-# specify an image to pull.
-#
-#infra_image = ""
-
-# Specify the locking mechanism to use; valid values are "shm" and "file".
-# Change the default only if you are sure of what you are doing, in general
-# "file" is useful only on platforms where cgo is not available for using the
-# faster "shm" lock type. You may need to run "podman system renumber" after
-# you change the lock type.
-#
-#lock_type** = "shm"
-
-# MultiImageArchive - if true, the container engine allows for storing archives
-# (e.g., of the docker-archive transport) with multiple images. By default,
-# Podman creates single-image archives.
-#
-#multi_image_archive = "false"
-
-# Default engine namespace
-# If engine is joined to a namespace, it will see only containers and pods
-# that were created in the same namespace, and will create new containers and
-# pods in that namespace.
-# The default namespace is "", which corresponds to no namespace. When no
-# namespace is set, all containers and pods are visible.
-#
-#namespace = ""
-
-# Path to the slirp4netns binary
-#
-#network_cmd_path = ""
-
-# Default options to pass to the slirp4netns binary.
-# Valid options values are:
-#
-# - allow_host_loopback=true|false: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`).
-# Default is false.
-# - mtu=MTU: Specify the MTU to use for this network. (Default is `65520`).
-# - cidr=CIDR: Specify ip range to use for this network. (Default is `10.0.2.0/24`).
-# - enable_ipv6=true|false: Enable IPv6. Default is true. (Required for `outbound_addr6`).
-# - outbound_addr=INTERFACE: Specify the outbound interface slirp should bind to (ipv4 traffic only).
-# - outbound_addr=IPv4: Specify the outbound ipv4 address slirp should bind to.
-# - outbound_addr6=INTERFACE: Specify the outbound interface slirp should bind to (ipv6 traffic only).
-# - outbound_addr6=IPv6: Specify the outbound ipv6 address slirp should bind to.
-# - port_handler=rootlesskit: Use rootlesskit for port forwarding. Default.
-# Note: Rootlesskit changes the source IP address of incoming packets to a IP address in the container
-# network namespace, usually `10.0.2.100`. If your application requires the real source IP address,
-# e.g. web server logs, use the slirp4netns port handler. The rootlesskit port handler is also used for
-# rootless containers when connected to user-defined networks.
-# - port_handler=slirp4netns: Use the slirp4netns port forwarding, it is slower than rootlesskit but
-# preserves the correct source IP address. This port handler cannot be used for user-defined networks.
-#
-#network_cmd_options = []
-
-# Whether to use chroot instead of pivot_root in the runtime
-#
-#no_pivot_root = false
-
-# Number of locks available for containers and pods.
-# If this is changed, a lock renumber must be performed (e.g. with the
-# 'podman system renumber' command).
-#
-#num_locks = 2048
-
-# Set the exit policy of the pod when the last container exits.
-#pod_exit_policy = "continue"
-
-# Whether to pull new image before running a container
-#
-#pull_policy = "missing"
-
-# Indicates whether the application should be running in remote mode. This flag modifies the
-# --remote option on container engines. Setting the flag to true will default
-# `podman --remote=true` for access to the remote Podman service.
-#
-#remote = false
-
-# Default OCI runtime
-#
-#runtime = "crun"
-
-# List of the OCI runtimes that support --format=json. When json is supported
-# engine will use it for reporting nicer errors.
-#
-#runtime_supports_json = ["crun", "runc", "kata", "runsc", "krun"]
-
-# List of the OCI runtimes that supports running containers with KVM Separation.
-#
-#runtime_supports_kvm = ["kata", "krun"]
-
-# List of the OCI runtimes that supports running containers without cgroups.
-#
-#runtime_supports_nocgroups = ["crun", "krun"]
-
-# Default location for storing temporary container image content. Can be overridden with the TMPDIR environment
-# variable. If you specify "storage", then the location of the
-# container/storage tmp directory will be used.
-# image_copy_tmp_dir="/var/tmp"
-
-# Number of seconds to wait without a connection
-# before the `podman system service` times out and exits
-#
-#service_timeout = 5
-
-# Directory for persistent engine files (database, etc)
-# By default, this will be configured relative to where the containers/storage
-# stores containers
-# Uncomment to change location from this default
-#
-#static_dir = "/var/lib/containers/storage/libpod"
-
-# Number of seconds to wait for container to exit before sending kill signal.
-#
-#stop_timeout = 10
-
-# Number of seconds to wait before exit command in API process is given to.
-# This mimics Docker's exec cleanup behaviour, where the default is 5 minutes (value is in seconds).
-#
-#exit_command_delay = 300
-
-# map of service destinations
-#
-#[service_destinations]
-# [service_destinations.production]
-# URI to access the Podman service
-# Examples:
-# rootless "unix://run/user/$UID/podman/podman.sock" (Default)
-# rootful "unix://run/podman/podman.sock (Default)
-# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock
-# remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock
-#
-# uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock"
-# Path to file containing ssh identity key
-# identity = "~/.ssh/id_rsa"
-
-# Directory for temporary files. Must be tmpfs (wiped after reboot)
-#
-#tmp_dir = "/run/libpod"
-
-# Directory for libpod named volumes.
-# By default, this will be configured relative to where containers/storage
-# stores containers.
-# Uncomment to change location from this default.
-#
-#volume_path = "/var/lib/containers/storage/volumes"
-
-# Default timeout (in seconds) for volume plugin operations.
-# Plugins are external programs accessed via a REST API; this sets a timeout
-# for requests to that API.
-# A value of 0 is treated as no timeout.
-#volume_plugin_timeout = 5
-
-# Paths to look for a valid OCI runtime (crun, runc, kata, runsc, krun, etc)
-[engine.runtimes]
-#crun = [
-# "/usr/bin/crun",
-# "/usr/sbin/crun",
-# "/usr/local/bin/crun",
-# "/usr/local/sbin/crun",
-# "/sbin/crun",
-# "/bin/crun",
-# "/run/current-system/sw/bin/crun",
-#]
-
-#kata = [
-# "/usr/bin/kata-runtime",
-# "/usr/sbin/kata-runtime",
-# "/usr/local/bin/kata-runtime",
-# "/usr/local/sbin/kata-runtime",
-# "/sbin/kata-runtime",
-# "/bin/kata-runtime",
-# "/usr/bin/kata-qemu",
-# "/usr/bin/kata-fc",
-#]
-
-#runc = [
-# "/usr/bin/runc",
-# "/usr/sbin/runc",
-# "/usr/local/bin/runc",
-# "/usr/local/sbin/runc",
-# "/sbin/runc",
-# "/bin/runc",
-# "/usr/lib/cri-o-runc/sbin/runc",
-#]
-
-#runsc = [
-# "/usr/bin/runsc",
-# "/usr/sbin/runsc",
-# "/usr/local/bin/runsc",
-# "/usr/local/sbin/runsc",
-# "/bin/runsc",
-# "/sbin/runsc",
-# "/run/current-system/sw/bin/runsc",
-#]
-
-#krun = [
-# "/usr/bin/krun",
-# "/usr/local/bin/krun",
-#]
-
-[engine.volume_plugins]
-#testplugin = "/run/podman/plugins/test.sock"
-
-[machine]
-# Number of CPU's a machine is created with.
-#
-#cpus=1
-
-# The size of the disk in GB created when init-ing a podman-machine VM.
-#
-#disk_size=10
-
-# Default image URI when creating a new VM using `podman machine init`.
-# Options: On Linux/Mac, `testing`, `stable`, `next`. On Windows, the major
-# version of the OS (e.g `36`) for Fedora 36. For all platforms you can
-# alternatively specify a custom download URL to an image. Container engines
-# translate URIs $OS and $ARCH to the native OS and ARCH. URI
-# "https://example.com/$OS/$ARCH/foobar.ami" becomes
-# "https://example.com/linux/amd64/foobar.ami" on a Linux AMD machine.
-# The default value is `testing`.
-#
-# image = "testing"
-
-# Memory in MB a machine is created with.
-#
-#memory=2048
-
-# The username to use and create on the podman machine OS for rootless
-# container access.
-#
-#user = "core"
-
-# Host directories to be mounted as volumes into the VM by default.
-# Environment variables like $HOME as well as complete paths are supported for
-# the source and destination. An optional third field `:ro` can be used to
-# tell the container engines to mount the volume readonly.
-#
-# volumes = [
-# "$HOME:$HOME",
-#]
-
-# The [machine] table MUST be the last entry in this file.
-# (Unless another table is added)
-# TOML does not provide a way to end a table other than a further table being
-# defined, so every key hereafter will be part of [machine] and not the
-# main config.
+### Autogenerated by container.py ###
+
+# The containers configuration file specifies all of the available configuration
+# command-line options/flags for container engine tools like Podman & Buildah,
+# but in a TOML format that can be easily modified and versioned.
+
+# Please refer to containers.conf(5) for details of all configuration options.
+# Not all container engines implement all of the options.
+# All of the options have hard coded defaults and these options will override
+# the built in defaults. Users can then override these options via the command
+# line. Container engines will read containers.conf files in up to three
+# locations in the following order:
+# 1. /usr/share/containers/containers.conf
+# 2. /etc/containers/containers.conf
+# 3. $HOME/.config/containers/containers.conf (Rootless containers ONLY)
+# Items specified in the latter containers.conf, if they exist, override the
+# previous containers.conf settings, or the default settings.
+
+[containers]
+
+# List of annotation. Specified as
+# "key = value"
+# If it is empty or commented out, no annotations will be added
+#
+#annotations = []
+
+# Used to change the name of the default AppArmor profile of container engine.
+#
+#apparmor_profile = "container-default"
+
+# The hosts entries from the base hosts file are added to the containers hosts
+# file. This must be either an absolute path or as special values "image" which
+# uses the hosts file from the container image or "none" which means
+# no base hosts file is used. The default is "" which will use /etc/hosts.
+#
+#base_hosts_file = ""
+
+# Default way to to create a cgroup namespace for the container
+# Options are:
+# `private` Create private Cgroup Namespace for the container.
+# `host` Share host Cgroup Namespace with the container.
+#
+#cgroupns = "private"
+
+# Control container cgroup configuration
+# Determines whether the container will create CGroups.
+# Options are:
+# `enabled` Enable cgroup support within container
+# `disabled` Disable cgroup support, will inherit cgroups from parent
+# `no-conmon` Do not create a cgroup dedicated to conmon.
+#
+#cgroups = "enabled"
+
+# List of default capabilities for containers. If it is empty or commented out,
+# the default capabilities defined in the container engine will be added.
+#
+default_capabilities = [
+ "CHOWN",
+ "DAC_OVERRIDE",
+ "FOWNER",
+ "FSETID",
+ "KILL",
+ "NET_BIND_SERVICE",
+ "SETFCAP",
+ "SETGID",
+ "SETPCAP",
+ "SETUID",
+ "SYS_CHROOT"
+]
+
+# A list of sysctls to be set in containers by default,
+# specified as "name=value",
+# for example:"net.ipv4.ping_group_range=0 0".
+#
+default_sysctls = [
+ "net.ipv4.ping_group_range=0 0",
+]
+
+# A list of ulimits to be set in containers by default, specified as
+# "<ulimit name>=<soft limit>:<hard limit>", for example:
+# "nofile=1024:2048"
+# See setrlimit(2) for a list of resource names.
+# Any limit not specified here will be inherited from the process launching the
+# container engine.
+# Ulimits has limits for non privileged container engines.
+#
+#default_ulimits = [
+# "nofile=1280:2560",
+#]
+
+# List of devices. Specified as
+# "<device-on-host>:<device-on-container>:<permissions>", for example:
+# "/dev/sdc:/dev/xvdc:rwm".
+# If it is empty or commented out, only the default devices will be used
+#
+#devices = []
+
+# List of default DNS options to be added to /etc/resolv.conf inside of the container.
+#
+#dns_options = []
+
+# List of default DNS search domains to be added to /etc/resolv.conf inside of the container.
+#
+#dns_searches = []
+
+# Set default DNS servers.
+# This option can be used to override the DNS configuration passed to the
+# container. The special value "none" can be specified to disable creation of
+# /etc/resolv.conf in the container.
+# The /etc/resolv.conf file in the image will be used without changes.
+#
+#dns_servers = []
+
+# Environment variable list for the conmon process; used for passing necessary
+# environment variables to conmon or the runtime.
+#
+#env = [
+# "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+# "TERM=xterm",
+#]
+
+# Pass all host environment variables into the container.
+#
+#env_host = false
+
+# Set the ip for the host.containers.internal entry in the containers /etc/hosts
+# file. This can be set to "none" to disable adding this entry. By default it
+# will automatically choose the host ip.
+#
+# NOTE: When using podman machine this entry will never be added to the containers
+# hosts file instead the gvproxy dns resolver will resolve this hostname. Therefore
+# it is not possible to disable the entry in this case.
+#
+#host_containers_internal_ip = ""
+
+# Default proxy environment variables passed into the container.
+# The environment variables passed in include:
+# http_proxy, https_proxy, ftp_proxy, no_proxy, and the upper case versions of
+# these. This option is needed when host system uses a proxy but container
+# should not use proxy. Proxy environment variables specified for the container
+# in any other way will override the values passed from the host.
+#
+#http_proxy = true
+
+# Run an init inside the container that forwards signals and reaps processes.
+#
+#init = false
+
+# Container init binary, if init=true, this is the init binary to be used for containers.
+#
+#init_path = "/usr/libexec/podman/catatonit"
+
+# Default way to to create an IPC namespace (POSIX SysV IPC) for the container
+# Options are:
+# "host" Share host IPC Namespace with the container.
+# "none" Create shareable IPC Namespace for the container without a private /dev/shm.
+# "private" Create private IPC Namespace for the container, other containers are not allowed to share it.
+# "shareable" Create shareable IPC Namespace for the container.
+#
+#ipcns = "shareable"
+
+# keyring tells the container engine whether to create
+# a kernel keyring for use within the container.
+#
+#keyring = true
+
+# label tells the container engine whether to use container separation using
+# MAC(SELinux) labeling or not.
+# The label flag is ignored on label disabled systems.
+#
+#label = true
+
+# Logging driver for the container. Available options: k8s-file and journald.
+#
+#log_driver = "k8s-file"
+
+# 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
+# exceed conmon's read buffer. The file is truncated and re-opened so the
+# limit is never exceeded.
+#
+#log_size_max = -1
+
+# Specifies default format tag for container log messages.
+# This is useful for creating a specific tag for container log messages.
+# Containers logs default to truncated container ID as a tag.
+#
+#log_tag = ""
+
+# Default way to to create a Network namespace for the container
+# Options are:
+# `private` Create private Network Namespace for the container.
+# `host` Share host Network Namespace with the container.
+# `none` Containers do not use the network
+#
+#netns = "private"
+
+# Create /etc/hosts for the container. By default, container engine manage
+# /etc/hosts, automatically adding the container's own IP address.
+#
+#no_hosts = false
+
+# Default way to to create a PID namespace for the container
+# Options are:
+# `private` Create private PID Namespace for the container.
+# `host` Share host PID Namespace with the container.
+#
+#pidns = "private"
+
+# Maximum number of processes allowed in a container.
+#
+#pids_limit = 2048
+
+# Copy the content from the underlying image into the newly created volume
+# when the container is created instead of when it is started. If false,
+# the container engine will not copy the content until the container is started.
+# Setting it to true may have negative performance implications.
+#
+#prepare_volume_on_create = false
+
+# Path to the seccomp.json profile which is used as the default seccomp profile
+# for the runtime.
+#
+#seccomp_profile = "/usr/share/containers/seccomp.json"
+
+# Size of /dev/shm. Specified as <number><unit>.
+# Unit is optional, values:
+# b (bytes), k (kilobytes), m (megabytes), or g (gigabytes).
+# If the unit is omitted, the system uses bytes.
+#
+#shm_size = "65536k"
+
+# Set timezone in container. Takes IANA timezones as well as "local",
+# which sets the timezone in the container to match the host machine.
+#
+#tz = ""
+
+# Set umask inside the container
+#
+#umask = "0022"
+
+# Default way to to create a User namespace for the container
+# Options are:
+# `auto` Create unique User Namespace for the container.
+# `host` Share host User Namespace with the container.
+#
+#userns = "host"
+
+# Number of UIDs to allocate for the automatic container creation.
+# UIDs are allocated from the "container" UIDs listed in
+# /etc/subuid & /etc/subgid
+#
+#userns_size = 65536
+
+# Default way to to create a UTS namespace for the container
+# Options are:
+# `private` Create private UTS Namespace for the container.
+# `host` Share host UTS Namespace with the container.
+#
+#utsns = "private"
+
+# List of volumes. Specified as
+# "<directory-on-host>:<directory-in-container>:<options>", for example:
+# "/db:/var/lib/db:ro".
+# If it is empty or commented out, no volumes will be added
+#
+#volumes = []
+
+[secrets]
+#driver = "file"
+
+[secrets.opts]
+#root = "/example/directory"
+
+[network]
+
+# Network backend determines what network driver will be used to set up and tear down container networks.
+# Valid values are "cni" and "netavark".
+# The default value is empty which means that it will automatically choose CNI or netavark. If there are
+# already containers/images or CNI networks preset it will choose CNI.
+#
+# Before changing this value all containers must be stopped otherwise it is likely that
+# iptables rules and network interfaces might leak on the host. A reboot will fix this.
+#
+network_backend = "netavark"
+
+# Path to directory where CNI plugin binaries are located.
+#
+#cni_plugin_dirs = [
+# "/usr/local/libexec/cni",
+# "/usr/libexec/cni",
+# "/usr/local/lib/cni",
+# "/usr/lib/cni",
+# "/opt/cni/bin",
+#]
+
+# The network name of the default network to attach pods to.
+#
+#default_network = "podman"
+
+# The default subnet for the default network given in default_network.
+# If a network with that name does not exist, a new network using that name and
+# this subnet will be created.
+# Must be a valid IPv4 CIDR prefix.
+#
+#default_subnet = "10.88.0.0/16"
+
+# DefaultSubnetPools is a list of subnets and size which are used to
+# allocate subnets automatically for podman network create.
+# It will iterate through the list and will pick the first free subnet
+# with the given size. This is only used for ipv4 subnets, ipv6 subnets
+# are always assigned randomly.
+#
+#default_subnet_pools = [
+# {"base" = "10.89.0.0/16", "size" = 24},
+# {"base" = "10.90.0.0/15", "size" = 24},
+# {"base" = "10.92.0.0/14", "size" = 24},
+# {"base" = "10.96.0.0/11", "size" = 24},
+# {"base" = "10.128.0.0/9", "size" = 24},
+#]
+
+# Path to the directory where network configuration files are located.
+# For the CNI backend the default is "/etc/cni/net.d" as root
+# and "$HOME/.config/cni/net.d" as rootless.
+# For the netavark backend "/etc/containers/networks" is used as root
+# and "$graphroot/networks" as rootless.
+#
+#network_config_dir = "/etc/cni/net.d/"
+
+# Port to use for dns forwarding daemon with netavark in rootful bridge
+# mode and dns enabled.
+# Using an alternate port might be useful if other dns services should
+# run on the machine.
+#
+#dns_bind_port = 53
+
+[engine]
+# Index to the active service
+#
+#active_service = production
+
+# The compression format to use when pushing an image.
+# Valid options are: `gzip`, `zstd` and `zstd:chunked`.
+#
+#compression_format = "gzip"
+
+
+# Cgroup management implementation used for the runtime.
+# Valid options "systemd" or "cgroupfs"
+#
+#cgroup_manager = "systemd"
+
+# Environment variables to pass into conmon
+#
+#conmon_env_vars = [
+# "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+#]
+
+# Paths to look for the conmon container manager binary
+#
+#conmon_path = [
+# "/usr/libexec/podman/conmon",
+# "/usr/local/libexec/podman/conmon",
+# "/usr/local/lib/podman/conmon",
+# "/usr/bin/conmon",
+# "/usr/sbin/conmon",
+# "/usr/local/bin/conmon",
+# "/usr/local/sbin/conmon"
+#]
+
+# Enforces using docker.io for completing short names in Podman's compatibility
+# REST API. Note that this will ignore unqualified-search-registries and
+# short-name aliases defined in containers-registries.conf(5).
+#compat_api_enforce_docker_hub = true
+
+# Specify the keys sequence used to detach a container.
+# Format is a single character [a-Z] or a comma separated sequence of
+# `ctrl-<value>`, where `<value>` is one of:
+# `a-z`, `@`, `^`, `[`, `\`, `]`, `^` or `_`
+#
+#detach_keys = "ctrl-p,ctrl-q"
+
+# Determines whether engine will reserve ports on the host when they are
+# forwarded to containers. When enabled, when ports are forwarded to containers,
+# ports are held open by as long as the container is running, ensuring that
+# they cannot be reused by other programs on the host. However, this can cause
+# significant memory usage if a container has many ports forwarded to it.
+# Disabling this can save memory.
+#
+#enable_port_reservation = true
+
+# Environment variables to be used when running the container engine (e.g., Podman, Buildah).
+# For example "http_proxy=internal.proxy.company.com".
+# Note these environment variables will not be used within the container.
+# Set the env section under [containers] table, if you want to set environment variables for the container.
+#
+#env = []
+
+# Define where event logs will be stored, when events_logger is "file".
+#events_logfile_path=""
+
+# Sets the maximum size for events_logfile_path.
+# The size can be b (bytes), k (kilobytes), m (megabytes), or g (gigabytes).
+# The format for the size is `<number><unit>`, e.g., `1b` or `3g`.
+# If no unit is included then the size will be read in bytes.
+# When the limit is exceeded, the logfile will be rotated and the old one will be deleted.
+# If the maximum size is set to 0, then no limit will be applied,
+# and the logfile will not be rotated.
+#events_logfile_max_size = "1m"
+
+# Selects which logging mechanism to use for container engine events.
+# Valid values are `journald`, `file` and `none`.
+#
+#events_logger = "journald"
+
+# A is a list of directories which are used to search for helper binaries.
+#
+#helper_binaries_dir = [
+# "/usr/local/libexec/podman",
+# "/usr/local/lib/podman",
+# "/usr/libexec/podman",
+# "/usr/lib/podman",
+#]
+
+# Path to OCI hooks directories for automatically executed hooks.
+#
+#hooks_dir = [
+# "/usr/share/containers/oci/hooks.d",
+#]
+
+# Manifest Type (oci, v2s2, or v2s1) to use when pulling, pushing, building
+# container images. By default image pulled and pushed match the format of the
+# source image. Building/committing defaults to OCI.
+#
+#image_default_format = ""
+
+# Default transport method for pulling and pushing for images
+#
+#image_default_transport = "docker://"
+
+# Maximum number of image layers to be copied (pulled/pushed) simultaneously.
+# Not setting this field, or setting it to zero, will fall back to containers/image defaults.
+#
+#image_parallel_copies = 0
+
+# Tells container engines how to handle the builtin image volumes.
+# * bind: An anonymous named volume will be created and mounted
+# into the container.
+# * tmpfs: The volume is mounted onto the container as a tmpfs,
+# which allows users to create content that disappears when
+# the container is stopped.
+# * ignore: All volumes are just ignored and no action is taken.
+#
+#image_volume_mode = ""
+
+# Default command to run the infra container
+#
+#infra_command = "/pause"
+
+# Infra (pause) container image name for pod infra containers. When running a
+# pod, we start a `pause` process in a container to hold open the namespaces
+# associated with the pod. This container does nothing other then sleep,
+# reserving the pods resources for the lifetime of the pod. By default container
+# engines run a builtin container using the pause executable. If you want override
+# specify an image to pull.
+#
+#infra_image = ""
+
+# Specify the locking mechanism to use; valid values are "shm" and "file".
+# Change the default only if you are sure of what you are doing, in general
+# "file" is useful only on platforms where cgo is not available for using the
+# faster "shm" lock type. You may need to run "podman system renumber" after
+# you change the lock type.
+#
+#lock_type** = "shm"
+
+# MultiImageArchive - if true, the container engine allows for storing archives
+# (e.g., of the docker-archive transport) with multiple images. By default,
+# Podman creates single-image archives.
+#
+#multi_image_archive = "false"
+
+# Default engine namespace
+# If engine is joined to a namespace, it will see only containers and pods
+# that were created in the same namespace, and will create new containers and
+# pods in that namespace.
+# The default namespace is "", which corresponds to no namespace. When no
+# namespace is set, all containers and pods are visible.
+#
+#namespace = ""
+
+# Path to the slirp4netns binary
+#
+#network_cmd_path = ""
+
+# Default options to pass to the slirp4netns binary.
+# Valid options values are:
+#
+# - allow_host_loopback=true|false: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`).
+# Default is false.
+# - mtu=MTU: Specify the MTU to use for this network. (Default is `65520`).
+# - cidr=CIDR: Specify ip range to use for this network. (Default is `10.0.2.0/24`).
+# - enable_ipv6=true|false: Enable IPv6. Default is true. (Required for `outbound_addr6`).
+# - outbound_addr=INTERFACE: Specify the outbound interface slirp should bind to (ipv4 traffic only).
+# - outbound_addr=IPv4: Specify the outbound ipv4 address slirp should bind to.
+# - outbound_addr6=INTERFACE: Specify the outbound interface slirp should bind to (ipv6 traffic only).
+# - outbound_addr6=IPv6: Specify the outbound ipv6 address slirp should bind to.
+# - port_handler=rootlesskit: Use rootlesskit for port forwarding. Default.
+# Note: Rootlesskit changes the source IP address of incoming packets to a IP address in the container
+# network namespace, usually `10.0.2.100`. If your application requires the real source IP address,
+# e.g. web server logs, use the slirp4netns port handler. The rootlesskit port handler is also used for
+# rootless containers when connected to user-defined networks.
+# - port_handler=slirp4netns: Use the slirp4netns port forwarding, it is slower than rootlesskit but
+# preserves the correct source IP address. This port handler cannot be used for user-defined networks.
+#
+#network_cmd_options = []
+
+# Whether to use chroot instead of pivot_root in the runtime
+#
+#no_pivot_root = false
+
+# Number of locks available for containers and pods.
+# If this is changed, a lock renumber must be performed (e.g. with the
+# 'podman system renumber' command).
+#
+#num_locks = 2048
+
+# Set the exit policy of the pod when the last container exits.
+#pod_exit_policy = "continue"
+
+# Whether to pull new image before running a container
+#
+#pull_policy = "missing"
+
+# Indicates whether the application should be running in remote mode. This flag modifies the
+# --remote option on container engines. Setting the flag to true will default
+# `podman --remote=true` for access to the remote Podman service.
+#
+#remote = false
+
+# Default OCI runtime
+#
+#runtime = "crun"
+
+# List of the OCI runtimes that support --format=json. When json is supported
+# engine will use it for reporting nicer errors.
+#
+#runtime_supports_json = ["crun", "runc", "kata", "runsc", "krun"]
+
+# List of the OCI runtimes that supports running containers with KVM Separation.
+#
+#runtime_supports_kvm = ["kata", "krun"]
+
+# List of the OCI runtimes that supports running containers without cgroups.
+#
+#runtime_supports_nocgroups = ["crun", "krun"]
+
+# Default location for storing temporary container image content. Can be overridden with the TMPDIR environment
+# variable. If you specify "storage", then the location of the
+# container/storage tmp directory will be used.
+# image_copy_tmp_dir="/var/tmp"
+
+# Number of seconds to wait without a connection
+# before the `podman system service` times out and exits
+#
+#service_timeout = 5
+
+# Directory for persistent engine files (database, etc)
+# By default, this will be configured relative to where the containers/storage
+# stores containers
+# Uncomment to change location from this default
+#
+#static_dir = "/var/lib/containers/storage/libpod"
+
+# Number of seconds to wait for container to exit before sending kill signal.
+#
+#stop_timeout = 10
+
+# Number of seconds to wait before exit command in API process is given to.
+# This mimics Docker's exec cleanup behaviour, where the default is 5 minutes (value is in seconds).
+#
+#exit_command_delay = 300
+
+# map of service destinations
+#
+#[service_destinations]
+# [service_destinations.production]
+# URI to access the Podman service
+# Examples:
+# rootless "unix://run/user/$UID/podman/podman.sock" (Default)
+# rootful "unix://run/podman/podman.sock (Default)
+# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock
+# remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock
+#
+# uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock"
+# Path to file containing ssh identity key
+# identity = "~/.ssh/id_rsa"
+
+# Directory for temporary files. Must be tmpfs (wiped after reboot)
+#
+#tmp_dir = "/run/libpod"
+
+# Directory for libpod named volumes.
+# By default, this will be configured relative to where containers/storage
+# stores containers.
+# Uncomment to change location from this default.
+#
+#volume_path = "/var/lib/containers/storage/volumes"
+
+# Default timeout (in seconds) for volume plugin operations.
+# Plugins are external programs accessed via a REST API; this sets a timeout
+# for requests to that API.
+# A value of 0 is treated as no timeout.
+#volume_plugin_timeout = 5
+
+# Paths to look for a valid OCI runtime (crun, runc, kata, runsc, krun, etc)
+[engine.runtimes]
+#crun = [
+# "/usr/bin/crun",
+# "/usr/sbin/crun",
+# "/usr/local/bin/crun",
+# "/usr/local/sbin/crun",
+# "/sbin/crun",
+# "/bin/crun",
+# "/run/current-system/sw/bin/crun",
+#]
+
+#kata = [
+# "/usr/bin/kata-runtime",
+# "/usr/sbin/kata-runtime",
+# "/usr/local/bin/kata-runtime",
+# "/usr/local/sbin/kata-runtime",
+# "/sbin/kata-runtime",
+# "/bin/kata-runtime",
+# "/usr/bin/kata-qemu",
+# "/usr/bin/kata-fc",
+#]
+
+#runc = [
+# "/usr/bin/runc",
+# "/usr/sbin/runc",
+# "/usr/local/bin/runc",
+# "/usr/local/sbin/runc",
+# "/sbin/runc",
+# "/bin/runc",
+# "/usr/lib/cri-o-runc/sbin/runc",
+#]
+
+#runsc = [
+# "/usr/bin/runsc",
+# "/usr/sbin/runsc",
+# "/usr/local/bin/runsc",
+# "/usr/local/sbin/runsc",
+# "/bin/runsc",
+# "/sbin/runsc",
+# "/run/current-system/sw/bin/runsc",
+#]
+
+#krun = [
+# "/usr/bin/krun",
+# "/usr/local/bin/krun",
+#]
+
+[engine.volume_plugins]
+#testplugin = "/run/podman/plugins/test.sock"
+
+[machine]
+# Number of CPU's a machine is created with.
+#
+#cpus=1
+
+# The size of the disk in GB created when init-ing a podman-machine VM.
+#
+#disk_size=10
+
+# Default image URI when creating a new VM using `podman machine init`.
+# Options: On Linux/Mac, `testing`, `stable`, `next`. On Windows, the major
+# version of the OS (e.g `36`) for Fedora 36. For all platforms you can
+# alternatively specify a custom download URL to an image. Container engines
+# translate URIs $OS and $ARCH to the native OS and ARCH. URI
+# "https://example.com/$OS/$ARCH/foobar.ami" becomes
+# "https://example.com/linux/amd64/foobar.ami" on a Linux AMD machine.
+# The default value is `testing`.
+#
+# image = "testing"
+
+# Memory in MB a machine is created with.
+#
+#memory=2048
+
+# The username to use and create on the podman machine OS for rootless
+# container access.
+#
+#user = "core"
+
+# Host directories to be mounted as volumes into the VM by default.
+# Environment variables like $HOME as well as complete paths are supported for
+# the source and destination. An optional third field `:ro` can be used to
+# tell the container engines to mount the volume readonly.
+#
+# volumes = [
+# "$HOME:$HOME",
+#]
+
+# The [machine] table MUST be the last entry in this file.
+# (Unless another table is added)
+# TOML does not provide a way to end a table other than a further table being
+# defined, so every key hereafter will be part of [machine] and not the
+# main config.
diff --git a/data/templates/dhcp-server/10-override.conf.j2 b/data/templates/dhcp-server/10-override.conf.j2
deleted file mode 100644
index 1504b6808..000000000
--- a/data/templates/dhcp-server/10-override.conf.j2
+++ /dev/null
@@ -1,30 +0,0 @@
-### Autogenerated by dhcp_server.py ###
-{% set lease_file = '/config/dhcpd.leases' %}
-[Unit]
-Description=ISC DHCP IPv4 server
-Documentation=man:dhcpd(8)
-RequiresMountsFor=/run
-ConditionPathExists=
-ConditionPathExists=/run/dhcp-server/dhcpd.conf
-After=
-After=vyos-router.service
-
-[Service]
-Type=forking
-WorkingDirectory=
-WorkingDirectory=/run/dhcp-server
-RuntimeDirectory=dhcp-server
-RuntimeDirectoryPreserve=yes
-Environment=PID_FILE=/run/dhcp-server/dhcpd.pid CONFIG_FILE=/run/dhcp-server/dhcpd.conf LEASE_FILE={{ lease_file }}
-PIDFile=/run/dhcp-server/dhcpd.pid
-ExecStartPre=/bin/sh -ec '\
-touch ${LEASE_FILE}; \
-chown dhcpd:vyattacfg ${LEASE_FILE}* ; \
-chmod 664 ${LEASE_FILE}* ; \
-/usr/sbin/dhcpd -4 -t -T -q -user dhcpd -group vyattacfg -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} '
-ExecStart=
-ExecStart=/usr/sbin/dhcpd -4 -q -user dhcpd -group vyattacfg -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE}
-Restart=always
-
-[Install]
-WantedBy=multi-user.target
diff --git a/data/templates/dhcp-server/dhcpd.conf.j2 b/data/templates/dhcp-server/dhcpd.conf.j2
deleted file mode 100644
index 639526532..000000000
--- a/data/templates/dhcp-server/dhcpd.conf.j2
+++ /dev/null
@@ -1,250 +0,0 @@
-### Autogenerated by dhcp_server.py ###
-
-# For options please consult the following website:
-# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html
-#
-# log-facility local7;
-{% if hostfile_update is vyos_defined %}
-on release {
- set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name);
- set ClientIp = binary-to-ascii(10, 8, ".",leased-address);
- execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "release", "", ClientIp, "", "");
-}
-on expiry {
- set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name);
- set ClientIp = binary-to-ascii(10, 8, ".",leased-address);
- execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "release", "", ClientIp, "", "");
-}
-{% endif %}
-
-{{ 'use-host-decl-names on;' if host_decl_name is vyos_defined }}
-ddns-update-style {{ 'interim' if dynamic_dns_update is vyos_defined else 'none' }};
-option rfc3442-static-route code 121 = array of integer 8;
-option windows-static-route code 249 = array of integer 8;
-option wpad-url code 252 = text;
-option rfc8925-ipv6-only-preferred code 108 = unsigned integer 32;
-
-# Vendor specific options - Ubiquiti Networks
-option space ubnt;
-option ubnt.unifi-controller code 1 = ip-address;
-class "ubnt" {
- match if substring (option vendor-class-identifier , 0, 4) = "ubnt";
- option vendor-class-identifier "ubnt";
- vendor-option-space ubnt;
-}
-
-{% if global_parameters is vyos_defined %}
-# The following {{ global_parameters | length }} line(s) have been added as
-# global-parameters in the CLI and have not been validated !!!
-{% for parameter in global_parameters %}
-{{ parameter }}
-{% endfor %}
-
-{% endif %}
-{% if failover is vyos_defined %}
-# DHCP failover configuration
-failover peer "{{ failover.name }}" {
-{% if failover.status == 'primary' %}
- primary;
- mclt 1800;
- split 128;
-{% elif failover.status == 'secondary' %}
- secondary;
-{% endif %}
- address {{ failover.source_address }};
- port 647;
- peer address {{ failover.remote }};
- peer port 647;
- max-response-delay 30;
- max-unacked-updates 10;
- load balance max seconds 3;
-}
-{% endif %}
-{% if listen_address is vyos_defined %}
-
-# DHCP server serving relay subnet, we need a connector to the real world
-{% for address in listen_address %}
-# Connected subnet statement for listen-address {{ address }}
-subnet {{ address | network_from_ipv4 }} netmask {{ address | netmask_from_ipv4 }} { }
-{% endfor %}
-{% endif %}
-
-# Shared network configration(s)
-{% if shared_network_name is vyos_defined %}
-{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %}
-shared-network {{ network }} {
-{% if network_config.authoritative is vyos_defined %}
- authoritative;
-{% endif %}
-{% if network_config.name_server is vyos_defined %}
- option domain-name-servers {{ network_config.name_server | join(', ') }};
-{% endif %}
-{% if network_config.domain_name is vyos_defined %}
- option domain-name "{{ network_config.domain_name }}";
-{% endif %}
-{% if network_config.domain_search is vyos_defined %}
- option domain-search "{{ network_config.domain_search | join('", "') }}";
-{% endif %}
-{% if network_config.ntp_server is vyos_defined %}
- option ntp-servers {{ network_config.ntp_server | join(', ') }};
-{% endif %}
-{% if network_config.ping_check is vyos_defined %}
- ping-check true;
-{% endif %}
-{% if network_config.shared_network_parameters is vyos_defined %}
- # The following {{ network_config.shared_network_parameters | length }} line(s)
- # were added as shared-network-parameters in the CLI and have not been validated
-{% for parameter in network_config.shared_network_parameters %}
- {{ parameter }}
-{% endfor %}
-{% endif %}
-{% if network_config.subnet is vyos_defined %}
-{% for subnet, subnet_config in network_config.subnet.items() %}
-{% if subnet_config.description is vyos_defined %}
- # {{ subnet_config.description }}
-{% endif %}
- subnet {{ subnet | address_from_cidr }} netmask {{ subnet | netmask_from_cidr }} {
-{% if subnet_config.name_server is vyos_defined %}
- option domain-name-servers {{ subnet_config.name_server | join(', ') }};
-{% endif %}
-{% if subnet_config.domain_name is vyos_defined %}
- option domain-name "{{ subnet_config.domain_name }}";
-{% endif %}
-{% if subnet_config.domain_search is vyos_defined %}
- option domain-search "{{ subnet_config.domain_search | join('", "') }}";
-{% endif %}
-{% if subnet_config.ntp_server is vyos_defined %}
- option ntp-servers {{ subnet_config.ntp_server | join(', ') }};
-{% endif %}
-{% if subnet_config.pop_server is vyos_defined %}
- option pop-server {{ subnet_config.pop_server | join(', ') }};
-{% endif %}
-{% if subnet_config.smtp_server is vyos_defined %}
- option smtp-server {{ subnet_config.smtp_server | join(', ') }};
-{% endif %}
-{% if subnet_config.time_server is vyos_defined %}
- option time-servers {{ subnet_config.time_server | join(', ') }};
-{% endif %}
-{% if subnet_config.wins_server is vyos_defined %}
- option netbios-name-servers {{ subnet_config.wins_server | join(', ') }};
-{% endif %}
-{% if subnet_config.ipv6_only_preferred is vyos_defined %}
- option rfc8925-ipv6-only-preferred {{ subnet_config.ipv6_only_preferred }};
-{% endif %}
-{% if subnet_config.static_route is vyos_defined %}
-{% set static_default_route = '' %}
-{% if subnet_config.default_router is vyos_defined %}
-{% set static_default_route = ', ' ~ '0.0.0.0/0' | isc_static_route(subnet_config.default_router) %}
-{% endif %}
-{% if subnet_config.static_route is vyos_defined %}
-{% set rfc3442_routes = [] %}
-{% for route, route_options in subnet_config.static_route.items() %}
-{% set rfc3442_routes = rfc3442_routes.append(route | isc_static_route(route_options.next_hop)) %}
-{% endfor %}
- option rfc3442-static-route {{ rfc3442_routes | join(', ') }}{{ static_default_route }};
- option windows-static-route {{ rfc3442_routes | join(', ') }};
-{% endif %}
-{% endif %}
-{% if subnet_config.ip_forwarding is vyos_defined %}
- option ip-forwarding true;
-{% endif %}
-{% if subnet_config.default_router is vyos_defined %}
- option routers {{ subnet_config.default_router }};
-{% endif %}
-{% if subnet_config.server_identifier is vyos_defined %}
- option dhcp-server-identifier {{ subnet_config.server_identifier }};
-{% endif %}
-{% if subnet_config.subnet_parameters is vyos_defined %}
- # The following {{ subnet_config.subnet_parameters | length }} line(s) were added as
- # subnet-parameters in the CLI and have not been validated!!!
-{% for parameter in subnet_config.subnet_parameters %}
- {{ parameter }}
-{% endfor %}
-{% endif %}
-{% if subnet_config.tftp_server_name is vyos_defined %}
- option tftp-server-name "{{ subnet_config.tftp_server_name }}";
-{% endif %}
-{% if subnet_config.bootfile_name is vyos_defined %}
- option bootfile-name "{{ subnet_config.bootfile_name }}";
- filename "{{ subnet_config.bootfile_name }}";
-{% endif %}
-{% if subnet_config.bootfile_server is vyos_defined %}
- next-server {{ subnet_config.bootfile_server }};
-{% endif %}
-{% if subnet_config.bootfile_size is vyos_defined %}
- option boot-size {{ subnet_config.bootfile_size }};
-{% endif %}
-{% if subnet_config.time_offset is vyos_defined %}
- option time-offset {{ subnet_config.time_offset }};
-{% endif %}
-{% if subnet_config.wpad_url is vyos_defined %}
- option wpad-url "{{ subnet_config.wpad_url }}";
-{% endif %}
-{% if subnet_config.client_prefix_length is vyos_defined %}
- option subnet-mask {{ ('0.0.0.0/' ~ subnet_config.client_prefix_length) | netmask_from_cidr }};
-{% endif %}
-{% if subnet_config.lease is vyos_defined %}
- default-lease-time {{ subnet_config.lease }};
- max-lease-time {{ subnet_config.lease }};
-{% endif %}
-{% if network_config.ping_check is not vyos_defined and subnet_config.ping_check is vyos_defined %}
- ping-check true;
-{% endif %}
-{% if subnet_config.static_mapping is vyos_defined %}
-{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %}
- host {{ host | replace('_','-') if host_decl_name is vyos_defined else network | replace('_','-') ~ '_' ~ host | replace('_','-') }} {
-{% if host_config.ip_address is vyos_defined %}
- fixed-address {{ host_config.ip_address }};
-{% endif %}
- hardware ethernet {{ host_config.mac_address }};
-{% if host_config.static_mapping_parameters is vyos_defined %}
- # The following {{ host_config.static_mapping_parameters | length }} line(s) were added
- # as static-mapping-parameters in the CLI and have not been validated
-{% for parameter in host_config.static_mapping_parameters %}
- {{ parameter }}
-{% endfor %}
-{% endif %}
- }
-{% endfor %}
-{% endif %}
-{% if subnet_config.vendor_option.ubiquiti.unifi_controller is vyos_defined %}
- option ubnt.unifi-controller {{ subnet_config.vendor_option.ubiquiti.unifi_controller }};
-{% endif %}
-{% if subnet_config.range is vyos_defined %}
-{# pool configuration can only be used if there follows a range option #}
- pool {
-{% endif %}
-{% if subnet_config.enable_failover is vyos_defined %}
- failover peer "{{ failover.name }}";
- deny dynamic bootp clients;
-{% endif %}
-{% if subnet_config.range is vyos_defined %}
-{% for range, range_options in subnet_config.range.items() %}
- range {{ range_options.start }} {{ range_options.stop }};
-{% endfor %}
-{% endif %}
-{% if subnet_config.range is vyos_defined %}
-{# pool configuration can only be used if there follows a range option #}
- }
-{% endif %}
- }
-{% endfor %}
-{% endif %}
- on commit {
- set shared-networkname = "{{ network }}";
-{% if hostfile_update is vyos_defined %}
- set ClientIp = binary-to-ascii(10, 8, ".", leased-address);
- set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
- set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name, "empty_hostname");
- if not (ClientName = "empty_hostname") {
- set ClientDomain = pick-first-value(config-option domain-name, "..YYZ!");
- execute("/usr/libexec/vyos/system/on-dhcp-event.sh", "commit", ClientName, ClientIp, ClientMac, ClientDomain);
- } else {
- log(concat("Hostname is not defined for client with IP: ", ClientIP, " MAC: ", ClientMac));
- }
-{% endif %}
- }
-}
-
-{% endfor %}
-{% endif %}
diff --git a/data/templates/dhcp-server/dhcpdv6.conf.j2 b/data/templates/dhcp-server/dhcpdv6.conf.j2
deleted file mode 100644
index 5c3471316..000000000
--- a/data/templates/dhcp-server/dhcpdv6.conf.j2
+++ /dev/null
@@ -1,132 +0,0 @@
-### Autogenerated by dhcpv6_server.py ###
-
-# For options please consult the following website:
-# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html
-
-log-facility local7;
-{% if preference is vyos_defined %}
-option dhcp6.preference {{ preference }};
-{% endif %}
-
-{% if global_parameters.name_server is vyos_defined %}
-option dhcp6.name-servers {{ global_parameters.name_server | join(', ') }};
-{% endif %}
-
-# Vendor specific options - Cisco
-option space cisco code width 2 length width 2;
-option cisco.tftp-servers code 1 = array of ip6-address;
-option vsio.cisco code 9 = encapsulate cisco;
-
-# Shared network configration(s)
-{% if shared_network_name is vyos_defined %}
-{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %}
-shared-network {{ network }} {
-{% if network_config.common_options is vyos_defined %}
-{% if network_config.common_options.info_refresh_time is vyos_defined %}
- option dhcp6.info-refresh-time {{ network_config.common_options.info_refresh_time }};
-{% endif %}
-{% if network_config.common_options.domain_search is vyos_defined %}
- option dhcp6.domain-search "{{ network_config.common_options.domain_search | join('", "') }}";
-{% endif %}
-{% if network_config.common_options.name_server is vyos_defined %}
- option dhcp6.name-servers {{ network_config.common_options.name_server | join(', ') }};
-{% endif %}
-{% endif %}
-{% if network_config.subnet is vyos_defined %}
-{% for subnet, subnet_config in network_config.subnet.items() %}
- subnet6 {{ subnet }} {
-{% if subnet_config.address_range is vyos_defined %}
-{% if subnet_config.address_range.prefix is vyos_defined %}
-{% for prefix, prefix_config in subnet_config.address_range.prefix.items() %}
- range6 {{ prefix }} {{ "temporary" if prefix_config.temporary is vyos_defined }};
-{% endfor %}
-{% endif %}
-{% if subnet_config.address_range.start is vyos_defined %}
-{% for address, address_config in subnet_config.address_range.start.items() %}
- range6 {{ address }} {{ address_config.stop }};
-{% endfor %}
-{% endif %}
-{% endif %}
-{% if subnet_config.domain_search is vyos_defined %}
- option dhcp6.domain-search "{{ subnet_config.domain_search | join('", "') }}";
-{% endif %}
-{% if subnet_config.lease_time is vyos_defined %}
-{% if subnet_config.lease_time.default is vyos_defined %}
- default-lease-time {{ subnet_config.lease_time.default }};
-{% endif %}
-{% if subnet_config.lease_time.maximum is vyos_defined %}
- max-lease-time {{ subnet_config.lease_time.maximum }};
-{% endif %}
-{% if subnet_config.lease_time.minimum is vyos_defined %}
- min-lease-time {{ subnet_config.lease_time.minimum }};
-{% endif %}
-{% endif %}
-{% if subnet_config.name_server is vyos_defined %}
- option dhcp6.name-servers {{ subnet_config.name_server | join(', ') }};
-{% endif %}
-{% if subnet_config.nis_domain is vyos_defined %}
- option dhcp6.nis-domain-name "{{ subnet_config.nis_domain }}";
-{% endif %}
-{% if subnet_config.nis_server is vyos_defined %}
- option dhcp6.nis-servers {{ subnet_config.nis_server | join(', ') }};
-{% endif %}
-{% if subnet_config.nisplus_domain is vyos_defined %}
- option dhcp6.nisp-domain-name "{{ subnet_config.nisplus_domain }}";
-{% endif %}
-{% if subnet_config.nisplus_server is vyos_defined %}
- option dhcp6.nisp-servers {{ subnet_config.nisplus_server | join(', ') }};
-{% endif %}
-{% if subnet_config.sip_server is vyos_defined %}
-{% set server_ip = [] %}
-{% set server_fqdn = [] %}
-{% for address in subnet_config.sip_server %}
-{% if address | is_ipv6 %}
-{% set server_ip = server_ip.append(address) %}
-{% else %}
-{% set server_fqdn = server_fqdn.append(address) %}
-{% endif %}
-{% endfor %}
-{% if server_ip is vyos_defined and server_ip | length > 0 %}
- option dhcp6.sip-servers-addresses {{ server_ip | join(', ') }};
-{% endif %}
-{% if server_fqdn is vyos_defined and server_fqdn | length > 0 %}
- option dhcp6.sip-servers-names "{{ server_fqdn | join('", "') }}";
-{% endif %}
-{% endif %}
-{% if subnet_config.sntp_server is vyos_defined %}
- option dhcp6.sntp-servers {{ subnet_config.sntp_server | join(', ') }};
-{% endif %}
-{% if subnet_config.prefix_delegation.start is vyos_defined %}
-{% for prefix, prefix_config in subnet_config.prefix_delegation.start.items() %}
- prefix6 {{ prefix }} {{ prefix_config.stop }} /{{ prefix_config.prefix_length }};
-{% endfor %}
-{% endif %}
-{% if subnet_config.static_mapping is vyos_defined %}
-
- # begin configuration of static client mappings
-{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %}
- host {{ network | replace('_','-') }}_{{ host | replace('_','-') }} {
-{% if host_config.identifier is vyos_defined %}
- host-identifier option dhcp6.client-id {{ host_config.identifier }};
-{% endif %}
-{% if host_config.ipv6_address is vyos_defined %}
- fixed-address6 {{ host_config.ipv6_address }};
-{% endif %}
-{% if host_config.ipv6_prefix is vyos_defined %}
- fixed-prefix6 {{ host_config.ipv6_prefix }};
-{% endif %}
- }
-{% endfor %}
-{% endif %}
-{% if subnet_config.vendor_option.cisco.tftp_server is vyos_defined %}
- option cisco.tftp-servers {{ subnet_config.vendor_option.cisco.tftp_server | join(', ') }};
-{% endif %}
- }
-{% endfor %}
-{% endif %}
- on commit {
- set shared-networkname = "{{ network }}";
- }
-}
-{% endfor %}
-{% endif %}
diff --git a/data/templates/dhcp-server/kea-ctrl-agent.conf.j2 b/data/templates/dhcp-server/kea-ctrl-agent.conf.j2
new file mode 100644
index 000000000..74c63a7a0
--- /dev/null
+++ b/data/templates/dhcp-server/kea-ctrl-agent.conf.j2
@@ -0,0 +1,14 @@
+{
+ "Control-agent": {
+{% if failover is vyos_defined %}
+ "http-host": "{{ failover.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-dhcp4.conf.j2 b/data/templates/dhcp-server/kea-dhcp4.conf.j2
new file mode 100644
index 000000000..6ab13ab27
--- /dev/null
+++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2
@@ -0,0 +1,72 @@
+{
+ "Dhcp4": {
+ "interfaces-config": {
+ "interfaces": [ "*" ],
+ "dhcp-socket-type": "raw",
+ "service-sockets-max-retries": 5,
+ "service-sockets-retry-wait-time": 5000
+ },
+ "control-socket": {
+ "socket-type": "unix",
+ "socket-name": "/run/kea/dhcp4-ctrl-socket"
+ },
+ "lease-database": {
+ "type": "memfile",
+ "persist": true,
+ "name": "{{ lease_file }}"
+ },
+ "option-def": [
+ {
+ "name": "rfc3442-static-route",
+ "code": 121,
+ "type": "record",
+ "array": true,
+ "record-types": "uint8,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,uint8"
+ },
+ {
+ "name": "wpad-url",
+ "code": 252,
+ "type": "string"
+ },
+ {
+ "name": "unifi-controller",
+ "code": 1,
+ "type": "ipv4-address",
+ "space": "ubnt"
+ }
+ ],
+ "hooks-libraries": [
+{% if failover is vyos_defined %}
+ {
+ "library": "/usr/lib/{{ machine }}-linux-gnu/kea/hooks/libdhcp_ha.so",
+ "parameters": {
+ "high-availability": [{{ failover | kea_failover_json }}]
+ }
+ },
+{% endif %}
+{% if hostfile_update is vyos_defined %}
+ {
+ "library": "/usr/lib/{{ machine }}-linux-gnu/kea/hooks/libdhcp_run_script.so",
+ "parameters": {
+ "name": "/usr/libexec/vyos/system/on-dhcp-event.sh",
+ "sync": false
+ }
+ },
+{% endif %}
+ {
+ "library": "/usr/lib/{{ machine }}-linux-gnu/kea/hooks/libdhcp_lease_cmds.so",
+ "parameters": {}
+ }
+ ],
+{% if shared_network_name is vyos_defined %}
+ "shared-networks": {{ shared_network_name | kea_shared_network_json }}
+{% endif %}
+ }
+}
diff --git a/data/templates/dhcp-server/kea-dhcp6.conf.j2 b/data/templates/dhcp-server/kea-dhcp6.conf.j2
new file mode 100644
index 000000000..3ce4e6370
--- /dev/null
+++ b/data/templates/dhcp-server/kea-dhcp6.conf.j2
@@ -0,0 +1,48 @@
+{
+ "Dhcp6": {
+ "interfaces-config": {
+ "interfaces": [ "*" ],
+ "service-sockets-max-retries": 5,
+ "service-sockets-retry-wait-time": 5000
+ },
+ "control-socket": {
+ "socket-type": "unix",
+ "socket-name": "/run/kea/dhcp6-ctrl-socket"
+ },
+ "lease-database": {
+ "type": "memfile",
+ "persist": true,
+ "name": "{{ lease_file }}"
+ },
+ "hooks-libraries": [
+ {
+ "library": "/usr/lib/{{ machine }}-linux-gnu/kea/hooks/libdhcp_lease_cmds.so",
+ "parameters": {}
+ }
+ ],
+ "option-data": [
+{% if global_parameters.name_server is vyos_defined %}
+ {
+ "name": "dns-servers",
+ "code": 23,
+ "space": "dhcp6",
+ "csv-format": true,
+ "data": "{{ global_parameters.name_server | join(", ") }}"
+ }{{ ',' if preference is vyos_defined else '' }}
+{% endif %}
+{% if preference is vyos_defined %}
+ {
+ "name": "preference",
+ "code": 7,
+ "space": "dhcp6",
+ "csv-format": true,
+ "data": "{{ preference }}"
+ }
+{% endif %}
+ ],
+{% if shared_network_name is vyos_defined %}
+ "shared-networks": {{ shared_network_name | kea6_shared_network_json }}
+{% endif %}
+
+ }
+}
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index 6f81174ac..641dac44a 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -588,6 +588,14 @@ bgp route-reflector allow-outbound-policy
{% if parameters.tcp_keepalive.idle is vyos_defined and parameters.tcp_keepalive.interval is vyos_defined and parameters.tcp_keepalive.probes is vyos_defined %}
bgp tcp-keepalive {{ parameters.tcp_keepalive.idle }} {{ parameters.tcp_keepalive.interval }} {{ parameters.tcp_keepalive.probes }}
{% endif %}
+{% if srv6.locator is vyos_defined %}
+ segment-routing srv6
+ locator {{ srv6.locator }}
+ exit
+{% endif %}
+{% if sid.vpn.per_vrf.export is vyos_defined %}
+ sid vpn per-vrf export {{ sid.vpn.per_vrf.export }}
+{% endif %}
{% if timers.keepalive is vyos_defined and timers.holdtime is vyos_defined %}
timers bgp {{ timers.keepalive }} {{ timers.holdtime }}
{% endif %}
diff --git a/data/templates/frr/eigrpd.frr.j2 b/data/templates/frr/eigrpd.frr.j2
index 67f8a3ad1..3038a0b1d 100644
--- a/data/templates/frr/eigrpd.frr.j2
+++ b/data/templates/frr/eigrpd.frr.j2
@@ -1,21 +1,21 @@
-!
-router eigrp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
-{% if maximum_paths is vyos_defined %}
-maximum-paths {{ maximum_paths }}
-{% endif %}
-{% if metric.weights is vyos_defined %}
-metric weights {{ metric.weights }}
-{% endif %}
-{% if network is vyos_defined %}
-{% for net in network %}
-network {{ net }}
-{% endfor %}
-{% endif %}
-{% if redistribute is vyos_defined %}
-{% for protocol in redistribute %}
-redistribute {{ protocol }}
-{% endfor %}
-{% endif %}
-{% if variance is vyos_defined %}
-variance {{ variance }}
+!
+router eigrp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
+{% if maximum_paths is vyos_defined %}
+maximum-paths {{ maximum_paths }}
+{% endif %}
+{% if metric.weights is vyos_defined %}
+metric weights {{ metric.weights }}
+{% endif %}
+{% if network is vyos_defined %}
+{% for net in network %}
+network {{ net }}
+{% endfor %}
+{% endif %}
+{% if redistribute is vyos_defined %}
+{% for protocol in redistribute %}
+redistribute {{ protocol }}
+{% endfor %}
+{% endif %}
+{% if variance is vyos_defined %}
+variance {{ variance }}
{% endif %} \ No newline at end of file
diff --git a/data/templates/frr/zebra.segment_routing.frr.j2 b/data/templates/frr/zebra.segment_routing.frr.j2
new file mode 100644
index 000000000..7b12fcdd0
--- /dev/null
+++ b/data/templates/frr/zebra.segment_routing.frr.j2
@@ -0,0 +1,23 @@
+!
+{% if srv6.locator is vyos_defined %}
+segment-routing
+ srv6
+ locators
+{% for locator, locator_config in srv6.locator.items() %}
+ locator {{ locator }}
+{% if locator_config.prefix is vyos_defined %}
+ prefix {{ locator_config.prefix }} block-len {{ locator_config.block_len }} node-len {{ locator_config.node_len }} func-bits {{ locator_config.func_bits }}
+{% endif %}
+{% if locator_config.behavior_usid is vyos_defined %}
+ behavior usid
+{% endif %}
+ exit
+ !
+{% endfor %}
+ exit
+ !
+exit
+!
+exit
+!
+{% endif %}
diff --git a/data/templates/system/sysctl.conf.j2 b/data/templates/system/sysctl.conf.j2
index 59a19e157..db699c3d8 100644
--- a/data/templates/system/sysctl.conf.j2
+++ b/data/templates/system/sysctl.conf.j2
@@ -1,7 +1,7 @@
-# autogenerated by system_sysctl.py
-
-{% if parameter is vyos_defined %}
-{% for k, v in parameter.items() %}
-{{ k }} = {{ v.value }}
-{% endfor %}
-{% endif %}
+# autogenerated by system_sysctl.py
+
+{% if parameter is vyos_defined %}
+{% for k, v in parameter.items() %}
+{{ k }} = {{ v.value }}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/telegraf/telegraf.j2 b/data/templates/telegraf/telegraf.j2
index 02a9656da..9623bdec6 100644
--- a/data/templates/telegraf/telegraf.j2
+++ b/data/templates/telegraf/telegraf.j2
@@ -89,6 +89,8 @@
ignore_fs = ["devtmpfs", "devfs"]
[[inputs.diskio]]
[[inputs.mem]]
+[[inputs.net]]
+ ignore_protocol_stats = true
[[inputs.nstat]]
[[inputs.system]]
[[inputs.netstat]]
diff --git a/debian/control b/debian/control
index f20268444..08adc8a68 100644
--- a/debian/control
+++ b/debian/control
@@ -169,8 +169,7 @@ Depends:
# For "service dhcp-relay"
isc-dhcp-relay,
# For "service dhcp-server"
- isc-dhcp-server,
- python3-isc-dhcp-leases,
+ kea,
# End "service dhcp-server"
# For "service lldp"
lldpd,
@@ -280,6 +279,8 @@ Depends:
# For "run monitor traffic"
tcpdump,
# End "run monitor traffic"
+# For "show hardware dmi"
+ dmidecode,
# For "run show hardware storage smart"
smartmontools,
# For "run show hardware scsi"
diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst
index 64c60a780..cd88cf60c 100644
--- a/debian/vyos-1x.postinst
+++ b/debian/vyos-1x.postinst
@@ -21,14 +21,6 @@ if ! grep -q '^openvpn' /etc/passwd; then
adduser --quiet --firstuid 100 --system --group --shell /usr/sbin/nologin openvpn
fi
-# Enable 2FA/MFA support for SSH and local logins
-for file in /etc/pam.d/sshd /etc/pam.d/login
-do
- PAM_CONFIG="# Check 2FA/MFA authentication token if enabled (per user)\nauth required pam_google_authenticator.so nullok forward_pass\n"
- grep -qF -- "pam_google_authenticator.so" $file || \
- sed -i "/^# Standard Un\*x authentication\./i${PAM_CONFIG}" $file
-done
-
# We need to have a group for RADIUS service users to use it inside PAM rules
if ! grep -q '^radius' /etc/group; then
addgroup --firstgid 1000 --quiet radius
diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in
index 583de7ba9..081f7ed42 100644
--- a/interface-definitions/dhcp-server.xml.in
+++ b/interface-definitions/dhcp-server.xml.in
@@ -63,26 +63,16 @@
<constraintErrorMessage>Invalid DHCP failover peer status</constraintErrorMessage>
</properties>
</leafNode>
+ #include <include/pki/ca-certificate.xml.i>
+ #include <include/pki/certificate.xml.i>
</children>
</node>
- <leafNode name="global-parameters">
- <properties>
- <help>Additional global parameters for DHCP server. You must use the syntax of dhcpd.conf in this text-field. Using this without proper knowledge may result in a crashed DHCP server. Check system log to look for errors.</help>
- <multi/>
- </properties>
- </leafNode>
<leafNode name="hostfile-update">
<properties>
<help>Updating /etc/hosts file (per client lease)</help>
<valueless/>
</properties>
</leafNode>
- <leafNode name="host-decl-name">
- <properties>
- <help>Use host declaration name for forward DNS name</help>
- <valueless/>
- </properties>
- </leafNode>
#include <include/listen-address-ipv4.xml.i>
<tagNode name="shared-network-name">
<properties>
@@ -102,16 +92,9 @@
#include <include/dhcp/domain-name.xml.i>
#include <include/dhcp/domain-search.xml.i>
#include <include/dhcp/ntp-server.xml.i>
- #include <include/dhcp/ping-check.xml.i>
#include <include/generic-description.xml.i>
#include <include/generic-disable-node.xml.i>
#include <include/name-server-ipv4.xml.i>
- <leafNode name="shared-network-parameters">
- <properties>
- <help>Additional shared-network parameters for DHCP server. You must use the syntax of dhcpd.conf in this text-field. Using this without proper knowledge may result in a crashed DHCP server. Check system log to look for errors.</help>
- <multi/>
- </properties>
- </leafNode>
<tagNode name="subnet">
<properties>
<help>DHCP subnet for shared network</help>
@@ -162,6 +145,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/dhcp/captive-portal.xml.i>
<leafNode name="client-prefix-length">
<properties>
<help>Specifies the clients subnet mask as per RFC 950. If unset, subnet declaration is used.</help>
@@ -191,12 +175,6 @@
#include <include/dhcp/domain-search.xml.i>
#include <include/generic-description.xml.i>
#include <include/name-server-ipv4.xml.i>
- <leafNode name="enable-failover">
- <properties>
- <help>Enable DHCP failover support for this subnet</help>
- <valueless/>
- </properties>
- </leafNode>
<leafNode name="exclude">
<properties>
<help>IP address to exclude from DHCP lease range</help>
@@ -231,7 +209,6 @@
<defaultValue>86400</defaultValue>
</leafNode>
#include <include/dhcp/ntp-server.xml.i>
- #include <include/dhcp/ping-check.xml.i>
<leafNode name="pop-server">
<properties>
<help>IP address of POP3 server</help>
@@ -339,12 +316,6 @@
</constraint>
</properties>
</leafNode>
- <leafNode name="static-mapping-parameters">
- <properties>
- <help>Additional static-mapping parameters for DHCP server. Will be placed inside the "host" block of the mapping. You must use the syntax of dhcpd.conf in this text-field. Using this without proper knowledge may result in a crashed DHCP server. Check system log to look for errors.</help>
- <multi/>
- </properties>
- </leafNode>
</children>
</tagNode>
<tagNode name="static-route">
@@ -386,12 +357,6 @@
<constraintErrorMessage>Seconds must be between 0 and 4294967295 (49 days)</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="subnet-parameters">
- <properties>
- <help>Additional subnet parameters for DHCP server. You must use the syntax of dhcpd.conf in this text-field. Using this without proper knowledge may result in a crashed DHCP server. Check system log to look for errors.</help>
- <multi/>
- </properties>
- </leafNode>
<leafNode name="tftp-server-name">
<properties>
<help>TFTP server name</help>
@@ -435,6 +400,17 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="time-zone">
+ <properties>
+ <help>Time zone to send to clients. Uses RFC4833 options 100 and 101</help>
+ <completionHelp>
+ <script>timedatectl list-timezones</script>
+ </completionHelp>
+ <constraint>
+ <validator name="timezone" argument="--validate"/>
+ </constraint>
+ </properties>
+ </leafNode>
<node name="vendor-option">
<properties>
<help>Vendor Specific Options</help>
diff --git a/interface-definitions/dhcpv6-server.xml.in b/interface-definitions/dhcpv6-server.xml.in
index 9dff68a24..b37f79434 100644
--- a/interface-definitions/dhcpv6-server.xml.in
+++ b/interface-definitions/dhcpv6-server.xml.in
@@ -41,6 +41,21 @@
<children>
#include <include/generic-disable-node.xml.i>
#include <include/generic-description.xml.i>
+ <leafNode name="interface">
+ <properties>
+ <help>Optional interface for this shared network to accept requests from</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+ </properties>
+ </leafNode>
<node name="common-options">
<properties>
<help>Common options to distribute to all clients, including stateless clients</help>
@@ -79,7 +94,7 @@
<help>Parameters setting ranges for assigning IPv6 addresses</help>
</properties>
<children>
- <tagNode name="prefix">
+ <leafNode name="prefix">
<properties>
<help>IPv6 prefix defining range of addresses to assign</help>
<valueHelp>
@@ -89,16 +104,9 @@
<constraint>
<validator name="ipv6-prefix"/>
</constraint>
+ <multi/>
</properties>
- <children>
- <leafNode name="temporary">
- <properties>
- <help>Address range will be used for temporary addresses</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
- </tagNode>
+ </leafNode>
<tagNode name="start">
<properties>
<help>First in range of consecutive IPv6 addresses to assign</help>
@@ -127,6 +135,7 @@
</tagNode>
</children>
</node>
+ #include <include/dhcp/captive-portal.xml.i>
#include <include/dhcp/domain-search.xml.i>
<node name="lease-time">
<properties>
@@ -221,12 +230,12 @@
<help>Parameters relating to IPv6 prefix delegation</help>
</properties>
<children>
- <tagNode name="start">
+ <tagNode name="prefix">
<properties>
- <help>First in range of IPv6 addresses to be used in prefix delegation</help>
+ <help>IPv6 prefix to be used in prefix delegation</help>
<valueHelp>
<format>ipv6</format>
- <description>IPv6 address used in prefix delegation</description>
+ <description>IPv6 prefix used in prefix delegation</description>
</valueHelp>
<constraint>
<validator name="ipv6-address"/>
@@ -235,27 +244,28 @@
<children>
<leafNode name="prefix-length">
<properties>
- <help>Length in bits of prefixes to be delegated</help>
+ <help>Length in bits of prefix</help>
<valueHelp>
<format>u32:32-64</format>
- <description>Delagated prefix length (32-64)</description>
+ <description>Prefix length (32-64)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 32-64"/>
</constraint>
- <constraintErrorMessage>Delegated prefix length must be between 32 and 64</constraintErrorMessage>
+ <constraintErrorMessage>Prefix length must be between 32 and 64</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="stop">
+ <leafNode name="delegated-length">
<properties>
- <help>Last in range of IPv6 addresses to be used in prefix delegation</help>
+ <help>Length in bits of prefixes to be delegated</help>
<valueHelp>
- <format>ipv6</format>
- <description>IPv6 address used in prefix delegation</description>
+ <format>u32:32-64</format>
+ <description>Delegated prefix length (32-64)</description>
</valueHelp>
<constraint>
- <validator name="ipv6-address"/>
+ <validator name="numeric" argument="--range 32-96"/>
</constraint>
+ <constraintErrorMessage>Delegated prefix length must be between 32 and 96</constraintErrorMessage>
</properties>
</leafNode>
</children>
diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i
index 4e43298bc..c379ba95c 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -1639,6 +1639,66 @@
#include <include/port-number.xml.i>
</children>
</tagNode>
+<node name="srv6">
+ <properties>
+ <help>Segment-Routing SRv6 configuration</help>
+ </properties>
+ <children>
+ <leafNode name="locator">
+ <properties>
+ <help>Specify SRv6 locator</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>SRv6 locator name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="sid">
+ <properties>
+ <help>SID value for VRF</help>
+ </properties>
+ <children>
+ <node name="vpn">
+ <properties>
+ <help>Between current VRF and VPN</help>
+ </properties>
+ <children>
+ <node name="per-vrf">
+ <properties>
+ <help>SID per-VRF (both IPv4 and IPv6 address families)</help>
+ </properties>
+ <children>
+ <leafNode name="export">
+ <properties>
+ <help>For routes leaked from current VRF to VPN</help>
+ <completionHelp>
+ <list>auto</list>
+ </completionHelp>
+ <valueHelp>
+ <format>u32:1-1048575</format>
+ <description>SID allocation index</description>
+ </valueHelp>
+ <valueHelp>
+ <format>auto</format>
+ <description>Automatically assign a label</description>
+ </valueHelp>
+ <constraint>
+ <regex>auto</regex>
+ <validator name="numeric" argument="--range 1-1048575"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+</node>
<node name="timers">
<properties>
<help>BGP protocol timers</help>
diff --git a/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i
index ba097c6b5..399f2e1da 100644
--- a/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i
+++ b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i
@@ -1,3 +1,3 @@
-<!-- include start from include/constraint/alpha-numeric-hyphen-underscore.xml.i -->
+<!-- include start from constraint/alpha-numeric-hyphen-underscore.xml.i -->
<regex>[-_a-zA-Z0-9]+</regex>
<!-- include end -->
diff --git a/interface-definitions/include/constraint/dhcp-client-string-option.xml.i b/interface-definitions/include/constraint/dhcp-client-string-option.xml.i
index 76e0e5466..88257a9bb 100644
--- a/interface-definitions/include/constraint/dhcp-client-string-option.xml.i
+++ b/interface-definitions/include/constraint/dhcp-client-string-option.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from include/constraint/dhcp-client-string-option.xml.i -->
+<!-- include start from constraint/dhcp-client-string-option.xml.i -->
<regex>[-_a-zA-Z0-9\s]+</regex>
<regex>([a-fA-F0-9][a-fA-F0-9]:){2,}[a-fA-F0-9][a-fA-F0-9]</regex>
<!-- include end -->
diff --git a/interface-definitions/include/constraint/host-name.xml.i b/interface-definitions/include/constraint/host-name.xml.i
index cc9740c16..5943772a2 100644
--- a/interface-definitions/include/constraint/host-name.xml.i
+++ b/interface-definitions/include/constraint/host-name.xml.i
@@ -1,3 +1,3 @@
-<!-- include start from constraint/host-name.xml.i -->
-<regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex>
-<!-- include end -->
+<!-- include start from constraint/host-name.xml.i -->
+<regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex>
+<!-- include end -->
diff --git a/interface-definitions/include/dhcp/captive-portal.xml.i b/interface-definitions/include/dhcp/captive-portal.xml.i
new file mode 100644
index 000000000..643f055a8
--- /dev/null
+++ b/interface-definitions/include/dhcp/captive-portal.xml.i
@@ -0,0 +1,11 @@
+<!-- include start from dhcp/captive-portal.xml.i -->
+<leafNode name="captive-portal">
+ <properties>
+ <help>Captive portal API endpoint</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Captive portal API endpoint</description>
+ </valueHelp>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/dhcp/ping-check.xml.i b/interface-definitions/include/dhcp/ping-check.xml.i
deleted file mode 100644
index a506f68e4..000000000
--- a/interface-definitions/include/dhcp/ping-check.xml.i
+++ /dev/null
@@ -1,8 +0,0 @@
-<!-- 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/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i
index b8581b03e..e412fe58e 100644
--- a/interface-definitions/include/policy/route-common.xml.i
+++ b/interface-definitions/include/policy/route-common.xml.i
@@ -1,257 +1,257 @@
-<!-- include start from policy/route-common.xml.i -->
-#include <include/policy/route-rule-action.xml.i>
-#include <include/generic-description.xml.i>
-#include <include/firewall/firewall-mark.xml.i>
-#include <include/generic-disable-node.xml.i>
-<node name="fragment">
- <properties>
- <help>IP fragment match</help>
- </properties>
- <children>
- <leafNode name="match-frag">
- <properties>
- <help>Second and further fragments of fragmented packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="match-non-frag">
- <properties>
- <help>Head fragments or unfragmented packets</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="ipsec">
- <properties>
- <help>Inbound IPsec packets</help>
- </properties>
- <children>
- <leafNode name="match-ipsec">
- <properties>
- <help>Inbound IPsec packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="match-none">
- <properties>
- <help>Inbound non-IPsec packets</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="limit">
- <properties>
- <help>Rate limit using a token bucket filter</help>
- </properties>
- <children>
- <leafNode name="burst">
- <properties>
- <help>Maximum number of packets to allow in excess of rate</help>
- <valueHelp>
- <format>u32:0-4294967295</format>
- <description>Maximum number of packets to allow in excess of rate</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="rate">
- <properties>
- <help>Maximum average matching rate</help>
- <valueHelp>
- <format>u32:0-4294967295</format>
- <description>Maximum average matching rate</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-#include <include/firewall/log.xml.i>
-<leafNode name="protocol">
- <properties>
- <help>Protocol to match (protocol name, number, or "all")</help>
- <completionHelp>
- <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script>
- </completionHelp>
- <valueHelp>
- <format>all</format>
- <description>All IP protocols</description>
- </valueHelp>
- <valueHelp>
- <format>tcp_udp</format>
- <description>Both TCP and UDP</description>
- </valueHelp>
- <valueHelp>
- <format>0-255</format>
- <description>IP protocol number</description>
- </valueHelp>
- <valueHelp>
- <format>!&lt;protocol&gt;</format>
- <description>IP protocol number</description>
- </valueHelp>
- <constraint>
- <validator name="ip-protocol"/>
- </constraint>
- </properties>
- <defaultValue>all</defaultValue>
-</leafNode>
-<node name="recent">
- <properties>
- <help>Parameters for matching recently seen sources</help>
- </properties>
- <children>
- <leafNode name="count">
- <properties>
- <help>Source addresses seen more than N times</help>
- <valueHelp>
- <format>u32:1-255</format>
- <description>Source addresses seen more than N times</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-255"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="time">
- <properties>
- <help>Source addresses seen in the last N seconds</help>
- <valueHelp>
- <format>u32:0-4294967295</format>
- <description>Source addresses seen in the last N seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-<node name="set">
- <properties>
- <help>Packet modifications</help>
- </properties>
- <children>
- <leafNode name="connection-mark">
- <properties>
- <help>Connection marking</help>
- <valueHelp>
- <format>u32:0-2147483647</format>
- <description>Connection marking</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-2147483647"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="dscp">
- <properties>
- <help>Packet Differentiated Services Codepoint (DSCP)</help>
- <valueHelp>
- <format>u32:0-63</format>
- <description>DSCP number</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-63"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="mark">
- <properties>
- <help>Packet marking</help>
- <valueHelp>
- <format>u32:1-2147483647</format>
- <description>Packet marking</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-2147483647"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="table">
- <properties>
- <help>Routing table to forward packet with</help>
- <valueHelp>
- <format>u32:1-200</format>
- <description>Table number</description>
- </valueHelp>
- <valueHelp>
- <format>main</format>
- <description>Main table</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-200"/>
- <regex>(main)</regex>
- </constraint>
- <completionHelp>
- <list>main</list>
- <path>protocols static table</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="tcp-mss">
- <properties>
- <help>TCP Maximum Segment Size</help>
- <valueHelp>
- <format>u32:500-1460</format>
- <description>Explicitly set TCP MSS value</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 500-1460"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
-#include <include/firewall/state.xml.i>
-#include <include/firewall/tcp-flags.xml.i>
-#include <include/firewall/tcp-mss.xml.i>
-<node name="time">
- <properties>
- <help>Time to match rule</help>
- </properties>
- <children>
- <leafNode name="monthdays">
- <properties>
- <help>Monthdays to match rule on</help>
- </properties>
- </leafNode>
- <leafNode name="startdate">
- <properties>
- <help>Date to start matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="starttime">
- <properties>
- <help>Time of day to start matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="stopdate">
- <properties>
- <help>Date to stop matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="stoptime">
- <properties>
- <help>Time of day to stop matching rule</help>
- </properties>
- </leafNode>
- <leafNode name="utc">
- <properties>
- <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="weekdays">
- <properties>
- <help>Weekdays to match rule on</help>
- </properties>
- </leafNode>
- </children>
-</node>
-<!-- include end -->
+<!-- include start from policy/route-common.xml.i -->
+#include <include/policy/route-rule-action.xml.i>
+#include <include/generic-description.xml.i>
+#include <include/firewall/firewall-mark.xml.i>
+#include <include/generic-disable-node.xml.i>
+<node name="fragment">
+ <properties>
+ <help>IP fragment match</help>
+ </properties>
+ <children>
+ <leafNode name="match-frag">
+ <properties>
+ <help>Second and further fragments of fragmented packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="match-non-frag">
+ <properties>
+ <help>Head fragments or unfragmented packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="ipsec">
+ <properties>
+ <help>Inbound IPsec packets</help>
+ </properties>
+ <children>
+ <leafNode name="match-ipsec">
+ <properties>
+ <help>Inbound IPsec packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="match-none">
+ <properties>
+ <help>Inbound non-IPsec packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="limit">
+ <properties>
+ <help>Rate limit using a token bucket filter</help>
+ </properties>
+ <children>
+ <leafNode name="burst">
+ <properties>
+ <help>Maximum number of packets to allow in excess of rate</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Maximum number of packets to allow in excess of rate</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rate">
+ <properties>
+ <help>Maximum average matching rate</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Maximum average matching rate</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+#include <include/firewall/log.xml.i>
+<leafNode name="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All IP protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp_udp</format>
+ <description>Both TCP and UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+ <defaultValue>all</defaultValue>
+</leafNode>
+<node name="recent">
+ <properties>
+ <help>Parameters for matching recently seen sources</help>
+ </properties>
+ <children>
+ <leafNode name="count">
+ <properties>
+ <help>Source addresses seen more than N times</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Source addresses seen more than N times</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="time">
+ <properties>
+ <help>Source addresses seen in the last N seconds</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Source addresses seen in the last N seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="set">
+ <properties>
+ <help>Packet modifications</help>
+ </properties>
+ <children>
+ <leafNode name="connection-mark">
+ <properties>
+ <help>Connection marking</help>
+ <valueHelp>
+ <format>u32:0-2147483647</format>
+ <description>Connection marking</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="dscp">
+ <properties>
+ <help>Packet Differentiated Services Codepoint (DSCP)</help>
+ <valueHelp>
+ <format>u32:0-63</format>
+ <description>DSCP number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-63"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="mark">
+ <properties>
+ <help>Packet marking</help>
+ <valueHelp>
+ <format>u32:1-2147483647</format>
+ <description>Packet marking</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="table">
+ <properties>
+ <help>Routing table to forward packet with</help>
+ <valueHelp>
+ <format>u32:1-200</format>
+ <description>Table number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>main</format>
+ <description>Main table</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-200"/>
+ <regex>(main)</regex>
+ </constraint>
+ <completionHelp>
+ <list>main</list>
+ <path>protocols static table</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="tcp-mss">
+ <properties>
+ <help>TCP Maximum Segment Size</help>
+ <valueHelp>
+ <format>u32:500-1460</format>
+ <description>Explicitly set TCP MSS value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 500-1460"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+#include <include/firewall/state.xml.i>
+#include <include/firewall/tcp-flags.xml.i>
+#include <include/firewall/tcp-mss.xml.i>
+<node name="time">
+ <properties>
+ <help>Time to match rule</help>
+ </properties>
+ <children>
+ <leafNode name="monthdays">
+ <properties>
+ <help>Monthdays to match rule on</help>
+ </properties>
+ </leafNode>
+ <leafNode name="startdate">
+ <properties>
+ <help>Date to start matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="starttime">
+ <properties>
+ <help>Time of day to start matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="stopdate">
+ <properties>
+ <help>Date to stop matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="stoptime">
+ <properties>
+ <help>Time of day to stop matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="utc">
+ <properties>
+ <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="weekdays">
+ <properties>
+ <help>Weekdays to match rule on</help>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/protocol-tcp-udp.xml.i b/interface-definitions/include/protocol-tcp-udp.xml.i
index d7e6752ad..c186c026a 100644
--- a/interface-definitions/include/protocol-tcp-udp.xml.i
+++ b/interface-definitions/include/protocol-tcp-udp.xml.i
@@ -1,22 +1,22 @@
-<!-- include start from snmp/protocol.xml.i -->
-<leafNode name="protocol">
- <properties>
- <help>Protocol to be used (TCP/UDP)</help>
- <completionHelp>
- <list>udp tcp</list>
- </completionHelp>
- <valueHelp>
- <format>udp</format>
- <description>Listen protocol UDP</description>
- </valueHelp>
- <valueHelp>
- <format>tcp</format>
- <description>Listen protocol TCP</description>
- </valueHelp>
- <constraint>
- <regex>(udp|tcp)</regex>
- </constraint>
- </properties>
- <defaultValue>udp</defaultValue>
-</leafNode>
-<!-- include end -->
+<!-- include start from snmp/protocol.xml.i -->
+<leafNode name="protocol">
+ <properties>
+ <help>Protocol to be used (TCP/UDP)</help>
+ <completionHelp>
+ <list>udp tcp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>udp</format>
+ <description>Listen protocol UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp</format>
+ <description>Listen protocol TCP</description>
+ </valueHelp>
+ <constraint>
+ <regex>(udp|tcp)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>udp</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/rip/version.xml.i b/interface-definitions/include/rip/version.xml.i
index a35350aee..61458b28e 100644
--- a/interface-definitions/include/rip/version.xml.i
+++ b/interface-definitions/include/rip/version.xml.i
@@ -1,18 +1,18 @@
-<!-- include start from rip/version.xml.i -->
-<leafNode name="version">
- <properties>
- <help>Limit RIP protocol version</help>
- <valueHelp>
- <format>1</format>
- <description>Allow RIPv1 only</description>
- </valueHelp>
- <valueHelp>
- <format>2</format>
- <description>Allow RIPv2 only</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-2"/>
- </constraint>
- </properties>
-</leafNode>
-<!-- include end -->
+<!-- include start from rip/version.xml.i -->
+<leafNode name="version">
+ <properties>
+ <help>Limit RIP protocol version</help>
+ <valueHelp>
+ <format>1</format>
+ <description>Allow RIPv1 only</description>
+ </valueHelp>
+ <valueHelp>
+ <format>2</format>
+ <description>Allow RIPv2 only</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/version/dhcp-server-version.xml.i b/interface-definitions/include/version/dhcp-server-version.xml.i
index 330cb7d1b..7c4b5633e 100644
--- a/interface-definitions/include/version/dhcp-server-version.xml.i
+++ b/interface-definitions/include/version/dhcp-server-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/dhcp-server-version.xml.i -->
-<syntaxVersion component='dhcp-server' version='6'></syntaxVersion>
+<syntaxVersion component='dhcp-server' version='7'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/dhcpv6-server-version.xml.i b/interface-definitions/include/version/dhcpv6-server-version.xml.i
index 4b2cf40aa..ae4178c90 100644
--- a/interface-definitions/include/version/dhcpv6-server-version.xml.i
+++ b/interface-definitions/include/version/dhcpv6-server-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/dhcpv6-server-version.xml.i -->
-<syntaxVersion component='dhcpv6-server' version='1'></syntaxVersion>
+<syntaxVersion component='dhcpv6-server' version='2'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/protocols-segment-routing.xml.in b/interface-definitions/protocols-segment-routing.xml.in
new file mode 100644
index 000000000..d461e9c5d
--- /dev/null
+++ b/interface-definitions/protocols-segment-routing.xml.in
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="protocols">
+ <children>
+ <node name="segment-routing" owner="${vyos_conf_scripts_dir}/protocols_segment_routing.py">
+ <properties>
+ <help>Segment Routing</help>
+ <priority>900</priority>
+ </properties>
+ <children>
+ <node name="srv6">
+ <properties>
+ <help>Segment-Routing SRv6 configuration</help>
+ </properties>
+ <children>
+ <tagNode name="locator">
+ <properties>
+ <help>Segment Routing SRv6 locator</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="behavior-usid">
+ <properties>
+ <help>Set SRv6 behavior uSID</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="prefix">
+ <properties>
+ <help>SRv6 locator prefix</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>SRv6 locator prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="block-len">
+ <properties>
+ <help>Configure SRv6 locator block length in bits</help>
+ <valueHelp>
+ <format>u32:16-64</format>
+ <description>Specify SRv6 locator block length in bits</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 16-64"/>
+ </constraint>
+ </properties>
+ <defaultValue>40</defaultValue>
+ </leafNode>
+ <leafNode name="func-bits">
+ <properties>
+ <help>Configure SRv6 locator function length in bits</help>
+ <valueHelp>
+ <format>u32:0-64</format>
+ <description>Specify SRv6 locator function length in bits</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-64"/>
+ </constraint>
+ </properties>
+ <defaultValue>16</defaultValue>
+ </leafNode>
+ <leafNode name="node-len">
+ <properties>
+ <help>Configure SRv6 locator node length in bits</help>
+ <valueHelp>
+ <format>u32:16-64</format>
+ <description>Configure SRv6 locator node length in bits</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 16-64"/>
+ </constraint>
+ </properties>
+ <defaultValue>24</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/dhcp.xml.in b/op-mode-definitions/dhcp.xml.in
index 9c2e2be76..ceb321f3e 100644
--- a/op-mode-definitions/dhcp.xml.in
+++ b/op-mode-definitions/dhcp.xml.in
@@ -163,7 +163,7 @@
<properties>
<help>Restart DHCP server</help>
</properties>
- <command>if cli-shell-api existsActive service dhcp-server; then sudo systemctl restart isc-dhcp-server.service; else echo "DHCP server not configured"; fi</command>
+ <command>if cli-shell-api existsActive service dhcp-server; then sudo systemctl restart kea-dhcp4-server.service; else echo "DHCP server not configured"; fi</command>
</node>
<node name="relay-agent">
<properties>
@@ -182,7 +182,7 @@
<properties>
<help>Restart DHCPv6 server</help>
</properties>
- <command>if cli-shell-api existsActive service dhcpv6-server; then sudo systemctl restart isc-dhcp-server6.service; else echo "DHCPv6 server not configured"; fi</command>
+ <command>if cli-shell-api existsActive service dhcpv6-server; then sudo systemctl restart kea-dhcp6-server.service; else echo "DHCPv6 server not configured"; fi</command>
</node>
<node name="relay-agent">
<properties>
diff --git a/op-mode-definitions/generate-system-login-user.xml.in b/op-mode-definitions/generate-system-login-user.xml.in
index 237a13610..bd80840df 100755
--- a/op-mode-definitions/generate-system-login-user.xml.in
+++ b/op-mode-definitions/generate-system-login-user.xml.in
@@ -1,90 +1,90 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="generate">
- <children>
- <node name="system">
- <properties>
- <help>Generate system related parameters</help>
- </properties>
- <children>
- <node name="login">
- <properties>
- <help>Generate system login related parameters</help>
- </properties>
- <children>
- <tagNode name="username">
- <properties>
- <help>Username used for authentication</help>
- <completionHelp>
- <list>&lt;username&gt;</list>
- </completionHelp>
- </properties>
- <children>
- <node name="otp-key">
- <properties>
- <help>Generate OpenConnect OTP token</help>
- </properties>
- <children>
- <node name="hotp-time">
- <properties>
- <help>HOTP time-based token</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5"</command>
- <children>
- <tagNode name="rate-limit">
- <properties>
- <help>Duration of single time interval</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate-limit "$9"</command>
- <children>
- <tagNode name="rate-time">
- <properties>
- <help>The number of digits in the one-time password</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate-limit "$9" --rate-time "${11}" </command>
- <children>
- <tagNode name="window-size">
- <properties>
- <help>The number of digits in the one-time password</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate-limit "$9" --rate-time "${11}" --window-size "${13}"</command>
- </tagNode>
- </children>
- </tagNode>
- </children>
- </tagNode>
- <tagNode name="window-size">
- <properties>
- <help>The number of digits in the one-time password</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --window-size "${9}"</command>
- <children>
- <tagNode name="rate-limit">
- <properties>
- <help>Duration of single time interval</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate-limit "${11}" --window-size "${9}"</command>
- <children>
- <tagNode name="rate-time">
- <properties>
- <help>Duration of single time interval</help>
- </properties>
- <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate-limit "${11}" --rate-time "${13}" --window-size "${9}"</command>
- </tagNode>
- </children>
- </tagNode>
- </children>
- </tagNode>
- </children>
- </node>
- </children>
- </node>
- </children>
- </tagNode>
- </children>
- </node>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="generate">
+ <children>
+ <node name="system">
+ <properties>
+ <help>Generate system related parameters</help>
+ </properties>
+ <children>
+ <node name="login">
+ <properties>
+ <help>Generate system login related parameters</help>
+ </properties>
+ <children>
+ <tagNode name="username">
+ <properties>
+ <help>Username used for authentication</help>
+ <completionHelp>
+ <path>system login user</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <node name="otp-key">
+ <properties>
+ <help>Generate OpenConnect OTP token</help>
+ </properties>
+ <children>
+ <node name="hotp-time">
+ <properties>
+ <help>HOTP time-based token</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5"</command>
+ <children>
+ <tagNode name="rate-limit">
+ <properties>
+ <help>Duration of single time interval</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate-limit "$9"</command>
+ <children>
+ <tagNode name="rate-time">
+ <properties>
+ <help>The number of digits in the one-time password</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate-limit "$9" --rate-time "${11}" </command>
+ <children>
+ <tagNode name="window-size">
+ <properties>
+ <help>The number of digits in the one-time password</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate-limit "$9" --rate-time "${11}" --window-size "${13}"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="window-size">
+ <properties>
+ <help>The number of digits in the one-time password</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --window-size "${9}"</command>
+ <children>
+ <tagNode name="rate-limit">
+ <properties>
+ <help>Duration of single time interval</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate-limit "${11}" --window-size "${9}"</command>
+ <children>
+ <tagNode name="rate-time">
+ <properties>
+ <help>Duration of single time interval</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/generate_system_login_user.py --username "$5" --rate-limit "${11}" --rate-time "${13}" --window-size "${9}"</command>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/include/ospf/graceful-restart.xml.i b/op-mode-definitions/include/ospf/graceful-restart.xml.i
index 736d8f951..848b19828 100644
--- a/op-mode-definitions/include/ospf/graceful-restart.xml.i
+++ b/op-mode-definitions/include/ospf/graceful-restart.xml.i
@@ -1,6 +1,6 @@
<node name="graceful-restart">
<properties>
- <help>Show IPv4 OSPF Graceful Restart</help>
+ <help>Show OSPF Graceful Restart</help>
</properties>
<children>
<leafNode name="helper">
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index 44628a112..3a8118dcb 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -51,7 +51,7 @@
<properties>
<help>Monitor last lines of DHCP server log</help>
</properties>
- <command>journalctl --no-hostname --follow --boot --unit isc-dhcp-server.service</command>
+ <command>journalctl --no-hostname --follow --boot --unit kea-dhcp4-server.service</command>
</node>
<node name="client">
<properties>
@@ -81,7 +81,7 @@
<properties>
<help>Monitor last lines of DHCPv6 server log</help>
</properties>
- <command>journalctl --no-hostname --follow --boot --unit isc-dhcp-server6.service</command>
+ <command>journalctl --no-hostname --follow --boot --unit kea-dhcp6-server.service</command>
</node>
<node name="client">
<properties>
diff --git a/op-mode-definitions/show-bgp.xml.in b/op-mode-definitions/show-bgp.xml.in
index 3c212614c..8b1992432 100644
--- a/op-mode-definitions/show-bgp.xml.in
+++ b/op-mode-definitions/show-bgp.xml.in
@@ -100,6 +100,19 @@
</children>
</tagNode>
#include <include/vtysh-generic-wide.xml.i>
+ <node name="segment-routing">
+ <properties>
+ <help>BGP Segment Routing</help>
+ </properties>
+ <children>
+ <leafNode name="srv6">
+ <properties>
+ <help>BGP Segment Routing SRv6</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index 3a622cfb5..399c6acf8 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -78,7 +78,7 @@
<properties>
<help>Show log for DHCP server</help>
</properties>
- <command>journalctl --no-hostname --boot --unit isc-dhcp-server.service</command>
+ <command>journalctl --no-hostname --boot --unit kea-dhcp4-server.service</command>
</node>
<node name="client">
<properties>
@@ -108,7 +108,7 @@
<properties>
<help>Show log for DHCPv6 server</help>
</properties>
- <command>journalctl --no-hostname --boot --unit isc-dhcp-server6.service</command>
+ <command>journalctl --no-hostname --boot --unit kea-dhcp6-server.service</command>
</node>
<node name="client">
<properties>
diff --git a/op-mode-definitions/show-segment-routing.xml.in b/op-mode-definitions/show-segment-routing.xml.in
new file mode 100644
index 000000000..ebdb51a61
--- /dev/null
+++ b/op-mode-definitions/show-segment-routing.xml.in
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="segment-routing">
+ <properties>
+ <help>Show Segment Routing</help>
+ </properties>
+ <children>
+ <node name="srv6">
+ <properties>
+ <help>Segment Routing SRv6</help>
+ </properties>
+ <children>
+ <node name="locator">
+ <properties>
+ <help>Locator Information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index df7240c88..e57129ce5 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -125,6 +125,7 @@ class ConfigMgmt:
get_first_key=True)
self.max_revisions = int(d.get('commit_revisions', 0))
+ self.num_revisions = 0
self.locations = d.get('commit_archive', {}).get('location', [])
self.source_address = d.get('commit_archive',
{}).get('source_address', '')
@@ -233,7 +234,7 @@ Proceed ?'''
msg = ''
if not self._check_revision_number(rev):
- msg = f'Invalid revision number {rev}: must be 0 < rev < {maxrev}'
+ msg = f'Invalid revision number {rev}: must be 0 < rev < {self.num_revisions}'
return msg, 1
prompt_str = 'Proceed with reboot ?'
@@ -385,15 +386,8 @@ Proceed ?'''
_, _, netloc = url.netloc.rpartition("@")
redacted_location = urlunsplit(url._replace(netloc=netloc))
print(f" {redacted_location}", end=" ", flush=True)
- try:
- upload(archive_config_file, f'{location}/{remote_file}',
- source_host=source_address, raise_error=True)
- print("OK")
- except Exception as e:
- print("FAILED!")
- print()
- print(indent(str(e), " > "))
- print()
+ upload(archive_config_file, f'{location}/{remote_file}',
+ source_host=source_address)
# op-mode functions
#
@@ -560,8 +554,8 @@ Proceed ?'''
return len(l)
def _check_revision_number(self, rev: int) -> bool:
- maxrev = self._get_number_of_revisions()
- if not 0 <= rev < maxrev:
+ self.num_revisions = self._get_number_of_revisions()
+ if not 0 <= rev < self.num_revisions:
return False
return True
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index 09cfd43d3..d048901f0 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -160,6 +160,9 @@ class ConfigTree(object):
def _get_config(self):
return self.__config
+ def get_version_string(self):
+ return self.__version
+
def to_string(self, ordered_values=False):
config_string = self.__to_string(self.__config, ordered_values).decode()
config_string = "{0}\n{1}".format(config_string, self.__version)
diff --git a/python/vyos/kea.py b/python/vyos/kea.py
new file mode 100644
index 000000000..cb341e0f2
--- /dev/null
+++ b/python/vyos/kea.py
@@ -0,0 +1,319 @@
+# Copyright 2023 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 json
+import os
+import socket
+
+from datetime import datetime
+
+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 read_file
+
+kea4_options = {
+ 'name_server': 'domain-name-servers',
+ 'domain_name': 'domain-name',
+ 'domain_search': 'domain-search',
+ 'ntp_server': 'ntp-servers',
+ 'pop_server': 'pop-server',
+ 'smtp_server': 'smtp-server',
+ 'time_server': 'time-servers',
+ 'wins_server': 'netbios-name-servers',
+ 'default_router': 'routers',
+ 'server_identifier': 'dhcp-server-identifier',
+ 'tftp_server_name': 'tftp-server-name',
+ 'bootfile_size': 'boot-size',
+ 'time_offset': 'time-offset',
+ 'wpad_url': 'wpad-url',
+ 'ipv6_only_preferred': 'v6-only-preferred',
+ 'captive_portal': 'v4-captive-portal'
+}
+
+kea6_options = {
+ 'info_refresh_time': 'information-refresh-time',
+ 'name_server': 'dns-servers',
+ 'domain_search': 'domain-search',
+ 'nis_domain': 'nis-domain-name',
+ 'nis_server': 'nis-servers',
+ 'nisplus_domain': 'nisp-domain-name',
+ 'nisplus_server': 'nisp-servers',
+ 'sntp_server': 'sntp-servers',
+ 'captive_portal': 'v6-captive-portal'
+}
+
+def kea_parse_options(config):
+ options = []
+
+ for node, option_name in kea4_options.items():
+ if node not in config:
+ continue
+
+ value = ", ".join(config[node]) if isinstance(config[node], list) else config[node]
+ options.append({'name': option_name, 'data': value})
+
+ if 'client_prefix_length' in config:
+ options.append({'name': 'subnet-mask', 'data': netmask_from_cidr('0.0.0.0/' + config['client_prefix_length'])})
+
+ if 'ip_forwarding' in config:
+ options.append({'name': 'ip-forwarding', 'data': "true"})
+
+ if 'static_route' in config:
+ default_route = ''
+
+ if 'default_router' in config:
+ default_route = isc_static_route('0.0.0.0/0', config['default_router'])
+
+ routes = [isc_static_route(route, route_options['next_hop']) for route, route_options in config['static_route'].items()]
+
+ options.append({'name': 'rfc3442-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:
+ tz_string = f.read().split(b"\n")[-2].decode("utf-8")
+
+ options.append({'name': 'pcode', 'data': tz_string})
+ options.append({'name': 'tcode', 'data': config['time_zone']})
+
+ return options
+
+def kea_parse_subnet(subnet, config):
+ out = {'subnet': subnet}
+ options = kea_parse_options(config)
+
+ if 'bootfile_name' in config:
+ out['boot-file-name'] = config['bootfile_name']
+
+ if 'bootfile_server' in config:
+ out['next-server'] = config['bootfile_server']
+
+ if 'lease' in config:
+ out['valid-lifetime'] = int(config['lease'])
+ out['max-valid-lifetime'] = int(config['lease'])
+
+ if 'range' in config:
+ pools = []
+ for num, range_config in config['range'].items():
+ start, stop = range_config['start'], range_config['stop']
+ pools.append({'pool': f'{start} - {stop}'})
+ out['pools'] = pools
+
+ if 'static_mapping' in config:
+ reservations = []
+ for host, host_config in config['static_mapping'].items():
+ if 'disable' in host_config:
+ continue
+
+ reservations.append({
+ 'hw-address': host_config['mac_address'],
+ 'ip-address': host_config['ip_address']
+ })
+ out['reservations'] = reservations
+
+ unifi_controller = dict_search_args(config, 'vendor_option', 'ubiquiti', 'unifi_controller')
+ if unifi_controller:
+ options.append({
+ 'name': 'unifi-controller',
+ 'data': unifi_controller,
+ 'space': 'ubnt'
+ })
+
+ if options:
+ out['option-data'] = options
+
+ return out
+
+def kea6_parse_options(config):
+ options = []
+
+ if 'common_options' in config:
+ common_opt = config['common_options']
+
+ for node, option_name in kea6_options.items():
+ if node not in common_opt:
+ continue
+
+ value = ", ".join(common_opt[node]) if isinstance(common_opt[node], list) else common_opt[node]
+ options.append({'name': option_name, 'data': value})
+
+ for node, option_name in kea6_options.items():
+ if node not in config:
+ continue
+
+ value = ", ".join(config[node]) if isinstance(config[node], list) else config[node]
+ options.append({'name': option_name, 'data': value})
+
+ if 'sip_server' in config:
+ sip_servers = config['sip_server']
+
+ addrs = []
+ hosts = []
+
+ for server in sip_servers:
+ if is_ipv6(server):
+ addrs.append(server)
+ else:
+ hosts.append(server)
+
+ if addrs:
+ options.append({'name': 'sip-server-addr', 'data': ", ".join(addrs)})
+
+ if hosts:
+ options.append({'name': 'sip-server-dns', 'data': ", ".join(hosts)})
+
+ cisco_tftp = dict_search_args(config, 'vendor_option', 'cisco', 'tftp-server')
+ if cisco_tftp:
+ options.append({'name': 'tftp-servers', 'code': 2, 'space': 'cisco', 'data': cisco_tftp})
+
+ return options
+
+def kea6_parse_subnet(subnet, config):
+ out = {'subnet': subnet}
+ options = kea6_parse_options(config)
+
+ if 'address_range' in config:
+ addr_range = config['address_range']
+ pools = []
+
+ if 'prefix' in addr_range:
+ for prefix in addr_range['prefix']:
+ pools.append({'pool': prefix})
+
+ if 'start' in addr_range:
+ for start, range_conf in addr_range['start'].items():
+ stop = range_conf['stop']
+ pools.append({'pool': f'{start} - {stop}'})
+
+ out['pools'] = pools
+
+ if 'prefix_delegation' in config:
+ pd_pools = []
+
+ if 'prefix' in config['prefix_delegation']:
+ for prefix, pd_conf in config['prefix_delegation']['prefix'].items():
+ pd_pools.append({
+ 'prefix': prefix,
+ 'prefix-len': int(pd_conf['prefix_length']),
+ 'delegated-len': int(pd_conf['delegated_length'])
+ })
+
+ out['pd-pools'] = pd_pools
+
+ if 'lease_time' in config:
+ if 'default' in config['lease_time']:
+ out['valid-lifetime'] = int(config['lease_time']['default'])
+ if 'maximum' in config['lease_time']:
+ out['max-valid-lifetime'] = int(config['lease_time']['maximum'])
+ if 'minimum' in config['lease_time']:
+ out['min-valid-lifetime'] = int(config['lease_time']['minimum'])
+
+ if 'static_mapping' in config:
+ reservations = []
+ for host, host_config in config['static_mapping'].items():
+ if 'disable' in host_config:
+ continue
+
+ reservation = {}
+
+ if 'identifier' in host_config:
+ reservation['duid'] = host_config['identifier']
+
+ if 'ipv6_address' in host_config:
+ reservation['ip-addresses'] = [ host_config['ipv6_address'] ]
+
+ if 'ipv6_prefix' in host_config:
+ reservation['prefixes'] = [ host_config['ipv6_prefix'] ]
+
+ reservations.append(reservation)
+
+ out['reservations'] = reservations
+
+ if options:
+ out['option-data'] = options
+
+ return out
+
+def kea_parse_leases(lease_path):
+ contents = read_file(lease_path)
+ lines = contents.split("\n")
+ output = []
+
+ if len(lines) < 2:
+ return output
+
+ headers = lines[0].split(",")
+
+ for line in lines[1:]:
+ line_out = dict(zip(headers, line.split(",")))
+
+ lifetime = int(line_out['valid_lifetime'])
+ expiry = int(line_out['expire'])
+
+ line_out['start_timestamp'] = datetime.utcfromtimestamp(expiry - lifetime)
+ line_out['expire_timestamp'] = datetime.utcfromtimestamp(expiry) if expiry else None
+
+ output.append(line_out)
+
+ return output
+
+def _ctrl_socket_command(path, command, args=None):
+ if not os.path.exists(path):
+ return None
+
+ with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
+ sock.connect(path)
+
+ payload = {'command': command}
+ if args:
+ payload['arguments'] = args
+
+ sock.send(bytes(json.dumps(payload), 'utf-8'))
+ result = b''
+ while True:
+ data = sock.recv(4096)
+ result += data
+ if len(data) < 4096:
+ break
+
+ return json.loads(result.decode('utf-8'))
+
+def kea_get_active_config(inet):
+ ctrl_socket = f'/run/kea/dhcp{inet}-ctrl-socket'
+
+ config = _ctrl_socket_command(ctrl_socket, 'config-get')
+
+ if not config or 'result' not in config or config['result'] != 0:
+ return None
+
+ return config
+
+def kea_get_pool_from_subnet_id(config, inet, subnet_id):
+ shared_networks = dict_search_args(config, 'arguments', f'Dhcp{inet}', 'shared-networks')
+
+ if not shared_networks:
+ return None
+
+ for network in shared_networks:
+ if f'subnet{inet}' not in network:
+ continue
+
+ for subnet in network[f'subnet{inet}']:
+ if 'id' in subnet and int(subnet['id']) == int(subnet_id):
+ return network['name']
+
+ return None
diff --git a/python/vyos/load_config.py b/python/vyos/load_config.py
new file mode 100644
index 000000000..af563614d
--- /dev/null
+++ b/python/vyos/load_config.py
@@ -0,0 +1,200 @@
+# Copyright 2023 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/>.
+
+"""This module abstracts the loading of a config file into the running
+config. It provides several varieties of loading a config file, from the
+legacy version to the developing versions, as a means of offering
+alternatives for competing use cases, and a base for profiling the
+performance of each.
+"""
+
+import sys
+from pathlib import Path
+from tempfile import NamedTemporaryFile
+from typing import Union, Literal, TypeAlias, get_type_hints, get_args
+
+from vyos.config import Config
+from vyos.configtree import ConfigTree, DiffTree
+from vyos.configsource import ConfigSourceSession, VyOSError
+from vyos.component_version import from_string as version_from_string
+from vyos.component_version import from_system as version_from_system
+from vyos.migrator import Migrator, VirtualMigrator, MigratorError
+from vyos.utils.process import popen, DEVNULL
+
+Variety: TypeAlias = Literal['explicit', 'batch', 'tree', 'legacy']
+ConfigObj: TypeAlias = Union[str, ConfigTree]
+
+thismod = sys.modules[__name__]
+
+class LoadConfigError(Exception):
+ """Raised when an error occurs loading a config file.
+ """
+
+# utility functions
+
+def get_running_config(config: Config) -> ConfigTree:
+ return config.get_config_tree(effective=True)
+
+def get_proposed_config(config_file: str = None) -> ConfigTree:
+ config_str = Path(config_file).read_text()
+ return ConfigTree(config_str)
+
+def migration_needed(config_obj: ConfigObj) -> bool:
+ """Check if a migration is needed for the config object.
+ """
+ if not isinstance(config_obj, ConfigTree):
+ atree = get_proposed_config(config_obj)
+ else:
+ atree = config_obj
+ version_str = atree.get_version_string()
+ if not version_str:
+ return True
+ aversion = version_from_string(version_str.splitlines()[1])
+ bversion = version_from_system()
+ return aversion != bversion
+
+def check_session(strict: bool, switch: Variety) -> None:
+ """Check if we are in a config session, with no uncommitted changes, if
+ strict. This is not needed for legacy load, as these checks are
+ implicit.
+ """
+
+ if switch == 'legacy':
+ return
+
+ context = ConfigSourceSession()
+
+ if not context.in_session():
+ raise LoadConfigError('not in a config session')
+
+ if strict and context.session_changed():
+ raise LoadConfigError('commit or discard changes before loading config')
+
+# methods to call for each variety
+
+# explicit
+def diff_to_commands(ctree: ConfigTree, ntree: ConfigTree) -> list:
+ """Calculate the diff between the current and proposed config."""
+ # Calculate the diff between the current and new config tree
+ commands = DiffTree(ctree, ntree).to_commands()
+ # on an empty set of 'add' or 'delete' commands, to_commands
+ # returns '\n'; prune below
+ command_list = commands.splitlines()
+ command_list = [c for c in command_list if c]
+ return command_list
+
+def set_commands(cmds: list) -> None:
+ """Set commands in the config session."""
+ if not cmds:
+ print('no commands to set')
+ return
+ error_out = []
+ for op in cmds:
+ out, rc = popen(f'/opt/vyatta/sbin/my_{op}', shell=True, stderr=DEVNULL)
+ if rc != 0:
+ error_out.append(out)
+ continue
+ if error_out:
+ out = '\n'.join(error_out)
+ raise LoadConfigError(out)
+
+# legacy
+class LoadConfig(ConfigSourceSession):
+ """A subclass for calling 'loadFile'.
+ """
+ def load_config(self, file_name):
+ return self._run(['/bin/cli-shell-api','loadFile', file_name])
+
+# end methods to call for each variety
+
+def migrate(config_obj: ConfigObj) -> ConfigObj:
+ """Migrate a config object to the current version.
+ """
+ if isinstance(config_obj, ConfigTree):
+ config_file = NamedTemporaryFile(delete=False).name
+ Path(config_file).write_text(config_obj.to_string())
+ else:
+ config_file = config_obj
+
+ virtual_migration = VirtualMigrator(config_file)
+ migration = Migrator(config_file)
+ try:
+ virtual_migration.run()
+ migration.run()
+ except MigratorError as e:
+ raise LoadConfigError(e) from e
+ else:
+ if isinstance(config_obj, ConfigTree):
+ return ConfigTree(Path(config_file).read_text())
+ return config_file
+ finally:
+ if isinstance(config_obj, ConfigTree):
+ Path(config_file).unlink()
+
+def load_explicit(config_obj: ConfigObj):
+ """Explicit load from file or configtree.
+ """
+ config = Config()
+ ctree = get_running_config(config)
+ if isinstance(config_obj, ConfigTree):
+ ntree = config_obj
+ else:
+ ntree = get_proposed_config(config_obj)
+ # Calculate the diff between the current and proposed config
+ cmds = diff_to_commands(ctree, ntree)
+ # Set the commands in the config session
+ set_commands(cmds)
+
+def load_batch(config_obj: ConfigObj):
+ # requires legacy backend patch
+ raise NotImplementedError('batch loading not implemented')
+
+def load_tree(config_obj: ConfigObj):
+ # requires vyconf backend patch
+ raise NotImplementedError('tree loading not implemented')
+
+def load_legacy(config_obj: ConfigObj):
+ """Legacy load from file or configtree.
+ """
+ if isinstance(config_obj, ConfigTree):
+ config_file = NamedTemporaryFile(delete=False).name
+ Path(config_file).write_text(config_obj.to_string())
+ else:
+ config_file = config_obj
+
+ config = LoadConfig()
+
+ try:
+ config.load_config(config_file)
+ except VyOSError as e:
+ raise LoadConfigError(e) from e
+ finally:
+ if isinstance(config_obj, ConfigTree):
+ Path(config_file).unlink()
+
+def load(config_obj: ConfigObj, strict: bool = True,
+ switch: Variety = 'legacy'):
+ type_hints = get_type_hints(load)
+ switch_choice = get_args(type_hints['switch'])
+ if switch not in switch_choice:
+ raise ValueError(f'invalid switch: {switch}')
+
+ check_session(strict, switch)
+
+ if migration_needed(config_obj):
+ config_obj = migrate(config_obj)
+
+ func = getattr(thismod, f'load_{switch}')
+ func(config_obj)
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index fec44b571..b1efcd10b 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -452,7 +452,7 @@ def upload(local_path, urlstring, progressbar=False,
source_host='', source_port=0, timeout=10.0):
try:
progressbar = progressbar and is_interactive()
- urlc(urlstring, progressbar, source_host, source_port, timeout).upload(local_path)
+ urlc(urlstring, progressbar, False, source_host, source_port, timeout).upload(local_path)
except Exception as err:
print_error(f'Unable to upload "{urlstring}": {err}')
except KeyboardInterrupt:
diff --git a/python/vyos/system/disk.py b/python/vyos/system/disk.py
index f8e0fd1bf..b8a2c0f35 100644
--- a/python/vyos/system/disk.py
+++ b/python/vyos/system/disk.py
@@ -31,12 +31,17 @@ class DiskDetails:
def disk_cleanup(drive_path: str) -> None:
"""Clean up disk partition table (MBR and GPT)
+ Remove partition and device signatures.
Zeroize primary and secondary headers - first and last 17408 bytes
(512 bytes * 34 LBA) on a drive
Args:
drive_path (str): path to a drive that needs to be cleaned
"""
+ partitions: list[str] = partition_list(drive_path)
+ for partition in partitions:
+ run(f'wipefs -af {partition}')
+ run(f'wipefs -af {drive_path}')
run(f'sgdisk -Z {drive_path}')
diff --git a/python/vyos/system/grub.py b/python/vyos/system/grub.py
index 0ac16af9a..2692aaea1 100644
--- a/python/vyos/system/grub.py
+++ b/python/vyos/system/grub.py
@@ -138,6 +138,8 @@ def version_list(root_dir: str = '') -> list[str]:
versions_list: list[str] = []
for file in versions_files:
versions_list.append(file.stem)
+ versions_list.sort(reverse=True)
+
return versions_list
diff --git a/python/vyos/system/raid.py b/python/vyos/system/raid.py
index 13b99fa69..5b33d34da 100644
--- a/python/vyos/system/raid.py
+++ b/python/vyos/system/raid.py
@@ -19,7 +19,7 @@ from pathlib import Path
from shutil import copy
from dataclasses import dataclass
-from vyos.utils.process import cmd
+from vyos.utils.process import cmd, run
from vyos.system import disk
@@ -44,18 +44,11 @@ def raid_create(raid_members: list[str],
"""
raid_devices_num: int = len(raid_members)
raid_members_str: str = ' '.join(raid_members)
- if Path('/sys/firmware/efi').exists():
- for part in raid_members:
- drive: str = disk.partition_parent(part)
- command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}'
- cmd(command)
- else:
- for part in raid_members:
- drive: str = disk.partition_parent(part)
- command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}'
- cmd(command)
for part in raid_members:
- command: str = f'mdadm --zero-superblock {part}'
+ drive: str = disk.partition_parent(part)
+ # set partition type GUID for raid member; cf.
+ # https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
+ command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}'
cmd(command)
command: str = f'mdadm --create /dev/{raid_name} -R --metadata=1.0 \
--raid-devices={raid_devices_num} --level={raid_level} \
@@ -72,6 +65,20 @@ def raid_create(raid_members: list[str],
return raid
+def clear():
+ """Deactivate all RAID arrays"""
+ command: str = 'mdadm --examine --scan'
+ raid_config = cmd(command)
+ if not raid_config:
+ return
+ command: str = 'mdadm --run /dev/md?*'
+ run(command)
+ command: str = 'mdadm --assemble --scan --auto=yes --symlink=no'
+ run(command)
+ command: str = 'mdadm --stop --scan'
+ run(command)
+
+
def update_initramfs() -> None:
"""Update initramfs"""
mdadm_script = '/etc/initramfs-tools/scripts/local-top/mdadm'
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 2d4beeec2..f0a50e728 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -791,6 +791,106 @@ def range_to_regex(num_range):
regex = range_to_regex(num_range)
return f'({regex})'
+@register_filter('kea_failover_json')
+def kea_failover_json(config):
+ from json import dumps
+
+ source_addr = config['source_address']
+ remote_addr = config['remote']
+
+ data = {
+ 'this-server-name': os.uname()[1],
+ 'mode': 'hot-standby',
+ 'heartbeat-delay': 10000,
+ 'max-response-delay': 10000,
+ 'max-ack-delay': 5000,
+ 'max-unacked-clients': 0,
+ 'peers': [
+ {
+ 'name': os.uname()[1],
+ 'url': f'http://{source_addr}:647/',
+ 'role': 'standby' if config['status'] == 'secondary' else 'primary',
+ 'auto-failover': True
+ },
+ {
+ 'name': config['name'],
+ 'url': f'http://{remote_addr}:647/',
+ 'role': 'primary' if config['status'] == 'secondary' else 'standby',
+ 'auto-failover': True
+ }]
+ }
+
+ if 'ca_cert_file' in config:
+ data['trust-anchor'] = config['ca_cert_file']
+
+ if 'cert_file' in config:
+ data['cert-file'] = config['cert_file']
+
+ if 'cert_key_file' in config:
+ data['key-file'] = config['cert_key_file']
+
+ return dumps(data)
+
+@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 json import dumps
+ out = []
+
+ for name, config in shared_networks.items():
+ if 'disable' in config:
+ continue
+
+ network = {
+ 'name': name,
+ 'authoritative': ('authoritative' in config),
+ 'subnet4': []
+ }
+ options = kea_parse_options(config)
+
+ if 'subnet' in config:
+ for subnet, subnet_config in config['subnet'].items():
+ network['subnet4'].append(kea_parse_subnet(subnet, subnet_config))
+
+ if options:
+ network['option-data'] = options
+
+ out.append(network)
+
+ return dumps(out, indent=4)
+
+@register_filter('kea6_shared_network_json')
+def kea6_shared_network_json(shared_networks):
+ from vyos.kea import kea6_parse_options
+ from vyos.kea import kea6_parse_subnet
+ from json import dumps
+ out = []
+
+ for name, config in shared_networks.items():
+ if 'disable' in config:
+ continue
+
+ network = {
+ 'name': name,
+ 'subnet6': []
+ }
+ options = kea6_parse_options(config)
+
+ if 'interface' in config:
+ network['interface'] = config['interface']
+
+ if 'subnet' in config:
+ for subnet, subnet_config in config['subnet'].items():
+ network['subnet6'].append(kea6_parse_subnet(subnet, subnet_config))
+
+ if options:
+ network['option-data'] = options
+
+ out.append(network)
+
+ return dumps(out, indent=4)
+
@register_test('vyos_defined')
def vyos_defined(value, test_value=None, var_type=None):
"""
diff --git a/python/vyos/utils/file.py b/python/vyos/utils/file.py
index 9f27a7fb9..2af87a0ca 100644
--- a/python/vyos/utils/file.py
+++ b/python/vyos/utils/file.py
@@ -141,6 +141,14 @@ def chmod_2775(path):
bitmask = S_ISGID | S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH
chmod(path, bitmask)
+def chmod_775(path):
+ """ Make file executable by all """
+ from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IXOTH
+
+ bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | \
+ S_IROTH | S_IXOTH
+ chmod(path, bitmask)
+
def makedir(path, user=None, group=None):
if os.path.exists(path):
return
diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py
index 74099b502..0afaf695c 100644
--- a/python/vyos/utils/io.py
+++ b/python/vyos/utils/io.py
@@ -26,13 +26,18 @@ def print_error(str='', end='\n'):
sys.stderr.write(end)
sys.stderr.flush()
-def ask_input(question, default='', numeric_only=False, valid_responses=[]):
+def ask_input(question, default='', numeric_only=False, valid_responses=[],
+ no_echo=False):
+ from getpass import getpass
question_out = question
if default:
question_out += f' (Default: {default})'
response = ''
while True:
- response = input(question_out + ' ').strip()
+ if not no_echo:
+ response = input(question_out + ' ').strip()
+ else:
+ response = getpass(question_out + ' ').strip()
if not response and default:
return default
if numeric_only:
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py
index 2a0808fca..997ee6309 100644
--- a/python/vyos/utils/network.py
+++ b/python/vyos/utils/network.py
@@ -201,6 +201,7 @@ def get_all_vrfs():
return data
def interface_list() -> list:
+ from vyos.ifconfig import Section
"""
Get list of interfaces in system
:rtype: list
@@ -519,3 +520,37 @@ def get_vxlan_vni_filter(interface: str) -> list:
os_configured_vnis.append(str(vniStart))
return os_configured_vnis
+
+# Calculate prefix length of an IPv6 range, where possible
+# Python-ified from source: https://gitlab.isc.org/isc-projects/dhcp/-/blob/master/keama/confparse.c#L4591
+def ipv6_prefix_length(low, high):
+ import socket
+
+ bytemasks = [0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff]
+
+ try:
+ lo = bytearray(socket.inet_pton(socket.AF_INET6, low))
+ hi = bytearray(socket.inet_pton(socket.AF_INET6, high))
+ except:
+ return None
+
+ xor = bytearray(a ^ b for a, b in zip(lo, hi))
+
+ plen = 0
+ while plen < 128 and xor[plen // 8] == 0:
+ plen += 8
+
+ if plen == 128:
+ return plen
+
+ for i in range((plen // 8) + 1, 16):
+ if xor[i] != 0:
+ return None
+
+ for i in range(8):
+ msk = ~xor[plen // 8] & 0xff
+
+ if msk == bytemasks[i]:
+ return plen + i + 1
+
+ return None
diff --git a/smoketest/config-tests/dialup-router-medium-vpn b/smoketest/config-tests/dialup-router-medium-vpn
index e10adbbc6..039a50594 100644
--- a/smoketest/config-tests/dialup-router-medium-vpn
+++ b/smoketest/config-tests/dialup-router-medium-vpn
@@ -257,7 +257,6 @@ set service dhcp-server shared-network-name LAN authoritative
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 default-router '192.168.0.1'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-name 'vyos.net'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-search 'vyos.net'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 enable-failover
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 lease '86400'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 name-server '192.168.0.1'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.200'
@@ -268,16 +267,12 @@ 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 IPTV mac-address '00:50:01:31:b5:f6'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus ip-address '192.168.0.60'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus mac-address '00:50:01:58:ac:95'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 ip-address '192.168.0.109'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 mac-address '00:50:01:bc:ac:51'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 ip-address '192.168.0.11'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 mac-address '00:50:01:70:b9:4d'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 ip-address '192.168.0.12'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 mac-address '00:50:01:70:b7:4f'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV ip-address '192.168.0.101'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV mac-address '00:50:01:ba:62:79'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping sand ip-address '192.168.0.110'
diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos
index 78dba3ee2..fca4964bf 100644
--- a/smoketest/configs/basic-vyos
+++ b/smoketest/configs/basic-vyos
@@ -1,6 +1,7 @@
interfaces {
ethernet eth0 {
address 192.168.0.1/24
+ address fe88::1/56
duplex auto
smp-affinity auto
speed auto
@@ -90,6 +91,23 @@ service {
}
}
}
+ dhcpv6-server {
+ shared-network-name LAN6 {
+ subnet fe88::/56 {
+ address-range {
+ prefix fe88::/56 {
+ temporary
+ }
+ }
+ prefix-delegation {
+ start fe88:0000:0000:0001:: {
+ prefix-length 64
+ stop fe88:0000:0000:0010::
+ }
+ }
+ }
+ }
+ }
dns {
forwarding {
allow-from 192.168.0.0/16
diff --git a/smoketest/configs/dialup-router-wireguard-ipv6 b/smoketest/configs/dialup-router-wireguard-ipv6
new file mode 100644
index 000000000..33afb9b04
--- /dev/null
+++ b/smoketest/configs/dialup-router-wireguard-ipv6
@@ -0,0 +1,1629 @@
+firewall {
+ all-ping enable
+ broadcast-ping disable
+ config-trap disable
+ group {
+ address-group DMZ-WEBSERVER {
+ address 172.16.36.10
+ address 172.16.36.40
+ address 172.16.36.20
+ }
+ address-group DMZ-RDP-SERVER {
+ address 172.16.33.40
+ }
+ address-group DOMAIN-CONTROLLER {
+ address 172.16.100.10
+ address 172.16.100.20
+ address 172.16.110.30
+ }
+ address-group VIDEO {
+ address 172.16.33.211
+ address 172.16.33.212
+ address 172.16.33.213
+ address 172.16.33.214
+ }
+ ipv6-network-group LOCAL-ADDRESSES {
+ network ff02::/64
+ network fe80::/10
+ }
+ network-group SSH-IN-ALLOW {
+ network 100.65.150.0/23
+ network 100.64.69.205/32
+ network 100.64.8.67/32
+ network 100.64.55.1/32
+ }
+ }
+ ipv6-name ALLOW-ALL-6 {
+ default-action accept
+ }
+ ipv6-name ALLOW-BASIC-6 {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ state {
+ invalid enable
+ }
+ }
+ rule 10 {
+ action accept
+ protocol icmpv6
+ }
+ }
+ ipv6-name ALLOW-ESTABLISHED-6 {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ state {
+ invalid enable
+ }
+ }
+ rule 10 {
+ action accept
+ destination {
+ group {
+ network-group LOCAL-ADDRESSES
+ }
+ }
+ protocol icmpv6
+ source {
+ address fe80::/10
+ }
+ }
+ rule 20 {
+ action accept
+ icmpv6 {
+ type echo-request
+ }
+ protocol icmpv6
+ }
+ rule 21 {
+ action accept
+ icmpv6 {
+ type destination-unreachable
+ }
+ protocol icmpv6
+ }
+ rule 22 {
+ action accept
+ icmpv6 {
+ type packet-too-big
+ }
+ protocol icmpv6
+ }
+ rule 23 {
+ action accept
+ icmpv6 {
+ type time-exceeded
+ }
+ protocol icmpv6
+ }
+ rule 24 {
+ action accept
+ icmpv6 {
+ type parameter-problem
+ }
+ protocol icmpv6
+ }
+ }
+ ipv6-name WAN-LOCAL-6 {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ state {
+ invalid enable
+ }
+ }
+ rule 10 {
+ action accept
+ destination {
+ address ff02::/64
+ }
+ protocol icmpv6
+ source {
+ address fe80::/10
+ }
+ }
+ rule 50 {
+ action accept
+ destination {
+ address fe80::/10
+ port 546
+ }
+ protocol udp
+ source {
+ address fe80::/10
+ port 547
+ }
+ }
+ }
+ ipv6-receive-redirects disable
+ ipv6-src-route disable
+ ip-src-route disable
+ log-martians enable
+ name DMZ-GUEST {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ }
+ name DMZ-LAN {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 100 {
+ action accept
+ destination {
+ group {
+ address-group DOMAIN-CONTROLLER
+ }
+ port 123,389,636
+ }
+ protocol tcp_udp
+ }
+ rule 300 {
+ action accept
+ destination {
+ group {
+ address-group DMZ-RDP-SERVER
+ }
+ port 3389
+ }
+ protocol tcp_udp
+ source {
+ address 172.16.36.20
+ }
+ }
+ }
+ name DMZ-LOCAL {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 50 {
+ action accept
+ destination {
+ address 172.16.254.30
+ port 53
+ }
+ protocol tcp_udp
+ }
+ rule 123 {
+ action accept
+ destination {
+ port 123
+ }
+ protocol udp
+ }
+ }
+ name DMZ-WAN {
+ default-action accept
+ }
+ name GUEST-DMZ {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ }
+ name GUEST-LAN {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ }
+ name GUEST-LOCAL {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 10 {
+ action accept
+ destination {
+ address 172.31.0.254
+ port 53
+ }
+ protocol tcp_udp
+ }
+ rule 11 {
+ action accept
+ destination {
+ port 67
+ }
+ protocol udp
+ }
+ rule 15 {
+ action accept
+ destination {
+ address 172.31.0.254
+ }
+ protocol icmp
+ }
+ rule 100 {
+ action accept
+ destination {
+ address 172.31.0.254
+ port 80,443
+ }
+ protocol tcp
+ }
+ }
+ name GUEST-WAN {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 25 {
+ action accept
+ destination {
+ port 25,587
+ }
+ protocol tcp
+ }
+ rule 53 {
+ action accept
+ destination {
+ port 53
+ }
+ protocol tcp_udp
+ }
+ rule 60 {
+ action accept
+ source {
+ address 172.31.0.200
+ }
+ }
+ rule 80 {
+ action accept
+ source {
+ address 172.31.0.200
+ }
+ }
+ rule 100 {
+ action accept
+ protocol icmp
+ }
+ rule 110 {
+ action accept
+ destination {
+ port 110,995
+ }
+ protocol tcp
+ }
+ rule 123 {
+ action accept
+ destination {
+ port 123
+ }
+ protocol udp
+ }
+ rule 143 {
+ action accept
+ destination {
+ port 143,993
+ }
+ protocol tcp
+ }
+ rule 200 {
+ action accept
+ destination {
+ port 80,443
+ }
+ protocol tcp
+ }
+ rule 500 {
+ action accept
+ destination {
+ port 500,4500
+ }
+ protocol udp
+ }
+ rule 600 {
+ action accept
+ destination {
+ port 5222-5224
+ }
+ protocol tcp
+ }
+ rule 601 {
+ action accept
+ destination {
+ port 3478-3497,4500,16384-16387,16393-16402
+ }
+ protocol udp
+ }
+ rule 1000 {
+ action accept
+ source {
+ address 172.31.0.184
+ }
+ }
+ }
+ name LAN-DMZ {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 22 {
+ action accept
+ destination {
+ port 22
+ }
+ protocol tcp
+ }
+ rule 100 {
+ action accept
+ destination {
+ group {
+ address-group DMZ-WEBSERVER
+ }
+ port 22
+ }
+ protocol tcp
+ }
+ }
+ name LAN-GUEST {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ }
+ name LAN-LOCAL {
+ default-action accept
+ }
+ name LAN-WAN {
+ default-action accept
+ rule 90 {
+ action accept
+ destination {
+ address 100.65.150.0/23
+ port 25
+ }
+ protocol tcp_udp
+ source {
+ group {
+ address-group VIDEO
+ }
+ }
+ }
+ rule 100 {
+ action drop
+ source {
+ group {
+ address-group VIDEO
+ }
+ }
+ }
+ }
+ name LOCAL-DMZ {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 100 {
+ action accept
+ destination {
+ address 172.16.36.40
+ port 80,443
+ }
+ protocol tcp
+ }
+ }
+ name LOCAL-GUEST {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 5 {
+ action accept
+ protocol icmp
+ }
+ rule 300 {
+ action accept
+ destination {
+ port 1900
+ }
+ protocol udp
+ }
+ }
+ name LOCAL-LAN {
+ default-action accept
+ }
+ name LOCAL-WAN {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 10 {
+ action accept
+ protocol icmp
+ }
+ rule 50 {
+ action accept
+ destination {
+ port 53
+ }
+ protocol tcp_udp
+ }
+ rule 80 {
+ action accept
+ destination {
+ port 80,443
+ }
+ protocol tcp
+ }
+ rule 123 {
+ action accept
+ destination {
+ port 123
+ }
+ protocol udp
+ }
+ rule 800 {
+ action accept
+ destination {
+ address 100.65.151.213
+ }
+ protocol udp
+ }
+ rule 805 {
+ action accept
+ destination {
+ address 100.65.151.2
+ }
+ protocol all
+ }
+ rule 1010 {
+ action accept
+ destination {
+ address 100.64.69.205
+ port 7705
+ }
+ protocol udp
+ source {
+ port 7705
+ }
+ }
+ rule 1990 {
+ action accept
+ destination {
+ address 100.64.55.1
+ port 10666
+ }
+ protocol udp
+ }
+ rule 2000 {
+ action accept
+ destination {
+ address 100.64.39.249
+ }
+ }
+ rule 10200 {
+ action accept
+ destination {
+ address 100.64.89.98
+ port 10200
+ }
+ protocol udp
+ source {
+ port 10200
+ }
+ }
+ }
+ name WAN-DMZ {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 100 {
+ action accept
+ destination {
+ address 172.16.36.10
+ port 80,443
+ }
+ protocol tcp
+ }
+ }
+ name WAN-GUEST {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 1000 {
+ action accept
+ destination {
+ address 172.31.0.184
+ }
+ }
+ rule 8000 {
+ action accept
+ destination {
+ address 172.31.0.200
+ port 10000
+ }
+ protocol udp
+ }
+ }
+ name WAN-LAN {
+ default-action drop
+ enable-default-log
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 1000 {
+ action accept
+ destination {
+ address 172.16.33.40
+ port 3389
+ }
+ protocol tcp
+ source {
+ group {
+ network-group SSH-IN-ALLOW
+ }
+ }
+ }
+ }
+ name WAN-LOCAL {
+ default-action drop
+ rule 1 {
+ action accept
+ state {
+ established enable
+ related enable
+ }
+ }
+ rule 2 {
+ action drop
+ log enable
+ state {
+ invalid enable
+ }
+ }
+ rule 22 {
+ action accept
+ destination {
+ port 22
+ }
+ protocol tcp
+ source {
+ group {
+ network-group SSH-IN-ALLOW
+ }
+ }
+ }
+ rule 1990 {
+ action accept
+ destination {
+ port 10666
+ }
+ protocol udp
+ source {
+ address 100.64.55.1
+ }
+ }
+ rule 10000 {
+ action accept
+ destination {
+ port 80,443
+ }
+ protocol tcp
+ }
+ rule 10100 {
+ action accept
+ destination {
+ port 10100
+ }
+ protocol udp
+ source {
+ port 10100
+ }
+ }
+ rule 10200 {
+ action accept
+ destination {
+ port 10200
+ }
+ protocol udp
+ source {
+ address 100.64.89.98
+ port 10200
+ }
+ }
+ }
+ options {
+ interface pppoe0 {
+ adjust-mss 1452
+ adjust-mss6 1432
+ }
+ }
+ receive-redirects disable
+ send-redirects enable
+ source-validation disable
+ syn-cookies enable
+ twa-hazards-protection disable
+}
+interfaces {
+ dummy dum0 {
+ address 172.16.254.30/32
+ }
+ ethernet eth0 {
+ duplex auto
+ offload {
+ gro
+ gso
+ sg
+ tso
+ }
+ ring-buffer {
+ rx 256
+ tx 256
+ }
+ speed auto
+ vif 5 {
+ address 172.16.37.254/24
+ ip {
+ ospf {
+ authentication {
+ md5 {
+ key-id 10 {
+ md5-key ospf
+ }
+ }
+ }
+ dead-interval 40
+ hello-interval 10
+ priority 1
+ retransmit-interval 5
+ transmit-delay 1
+ }
+ }
+ }
+ vif 10 {
+ address 172.16.33.254/24
+ address 172.16.40.254/24
+ }
+ vif 50 {
+ address 172.16.36.254/24
+ }
+ }
+ ethernet eth1 {
+ duplex auto
+ offload {
+ gro
+ gso
+ sg
+ tso
+ }
+ speed auto
+ vif 20 {
+ address 172.31.0.254/24
+ }
+ }
+ ethernet eth2 {
+ disable
+ duplex auto
+ offload {
+ gro
+ gso
+ sg
+ tso
+ }
+ speed auto
+ }
+ ethernet eth3 {
+ duplex auto
+ offload {
+ gro
+ gso
+ sg
+ tso
+ }
+ ring-buffer {
+ rx 256
+ tx 256
+ }
+ speed auto
+ vif 7 {
+ }
+ }
+ loopback lo {
+ address 172.16.254.30/32
+ }
+ pppoe pppoe0 {
+ authentication {
+ password vyos
+ user vyos
+ }
+ default-route force
+ dhcpv6-options {
+ pd 0 {
+ interface eth0.10 {
+ address 1
+ sla-id 10
+ }
+ interface eth1.20 {
+ address 1
+ sla-id 20
+ }
+ length 56
+ }
+ }
+ ipv6 {
+ address {
+ autoconf
+ }
+ }
+ no-peer-dns
+ source-interface eth3.7
+ }
+ wireguard wg100 {
+ address 172.16.252.128/31
+ mtu 1500
+ peer HR6 {
+ address 100.65.151.213
+ allowed-ips 0.0.0.0/0
+ port 10100
+ pubkey yLpi+UZuI019bmWH2h5fX3gStbpPPPLgEoYMyrdkOnQ=
+ }
+ port 10100
+ }
+ wireguard wg200 {
+ address 172.16.252.130/31
+ mtu 1500
+ peer WH56 {
+ address 80.151.69.205
+ allowed-ips 0.0.0.0/0
+ port 10200
+ pubkey XQbkj6vnKKBJfJQyThXysU0iGxCvEOEb31kpaZgkrD8=
+ }
+ port 10200
+ }
+ wireguard wg666 {
+ address 172.29.0.1/31
+ mtu 1500
+ peer WH34 {
+ address 100.65.55.1
+ allowed-ips 0.0.0.0/0
+ port 10666
+ pubkey yaTN4+xAafKM04D+Baeg5GWfbdaw35TE9HQivwRgAk0=
+ }
+ port 10666
+ }
+}
+nat {
+ destination {
+ rule 8000 {
+ destination {
+ port 10000
+ }
+ inbound-interface pppoe0
+ protocol udp
+ translation {
+ address 172.31.0.200
+ }
+ }
+ }
+ source {
+ rule 50 {
+ outbound-interface pppoe0
+ source {
+ address 100.64.0.0/24
+ }
+ translation {
+ address masquerade
+ }
+ }
+ rule 100 {
+ outbound-interface pppoe0
+ source {
+ address 172.16.32.0/21
+ }
+ translation {
+ address masquerade
+ }
+ }
+ rule 200 {
+ outbound-interface pppoe0
+ source {
+ address 172.16.100.0/24
+ }
+ translation {
+ address masquerade
+ }
+ }
+ rule 300 {
+ outbound-interface pppoe0
+ source {
+ address 172.31.0.0/24
+ }
+ translation {
+ address masquerade
+ }
+ }
+ rule 400 {
+ outbound-interface pppoe0
+ source {
+ address 172.18.200.0/21
+ }
+ translation {
+ address masquerade
+ }
+ }
+ rule 1000 {
+ destination {
+ address 192.168.189.0/24
+ }
+ outbound-interface wg666
+ source {
+ address 172.16.32.0/21
+ }
+ translation {
+ address 172.29.0.1
+ }
+ }
+ rule 1001 {
+ destination {
+ address 192.168.189.0/24
+ }
+ outbound-interface wg666
+ source {
+ address 172.16.100.0/24
+ }
+ translation {
+ address 172.29.0.1
+ }
+ }
+ }
+}
+policy {
+ route-map MAP-OSPF-CONNECTED {
+ rule 1 {
+ action deny
+ match {
+ interface eth1.20
+ }
+ }
+ rule 20 {
+ action permit
+ match {
+ interface eth0.10
+ }
+ }
+ rule 40 {
+ action permit
+ match {
+ interface eth0.50
+ }
+ }
+ }
+}
+protocols {
+ bfd {
+ peer 172.16.252.129 {
+ }
+ peer 172.16.252.131 {
+ }
+ peer 172.18.254.201 {
+ }
+ }
+ bgp 64503 {
+ address-family {
+ ipv4-unicast {
+ network 172.16.32.0/21 {
+ }
+ network 172.16.100.0/24 {
+ }
+ network 172.16.252.128/31 {
+ }
+ network 172.16.252.130/31 {
+ }
+ network 172.16.254.30/32 {
+ }
+ network 172.18.0.0/16 {
+ }
+ }
+ }
+ neighbor 172.16.252.129 {
+ peer-group WIREGUARD
+ }
+ neighbor 172.16.252.131 {
+ peer-group WIREGUARD
+ }
+ neighbor 172.18.254.201 {
+ address-family {
+ ipv4-unicast {
+ nexthop-self {
+ }
+ }
+ }
+ bfd {
+ }
+ remote-as 64503
+ update-source dum0
+ }
+ parameters {
+ default {
+ no-ipv4-unicast
+ }
+ log-neighbor-changes
+ }
+ peer-group WIREGUARD {
+ address-family {
+ ipv4-unicast {
+ soft-reconfiguration {
+ inbound
+ }
+ }
+ }
+ bfd
+ remote-as external
+ }
+ timers {
+ holdtime 30
+ keepalive 10
+ }
+ }
+ ospf {
+ area 0 {
+ network 172.16.254.30/32
+ network 172.16.37.0/24
+ network 172.18.201.0/24
+ network 172.18.202.0/24
+ network 172.18.203.0/24
+ network 172.18.204.0/24
+ }
+ default-information {
+ originate {
+ always
+ metric-type 2
+ }
+ }
+ log-adjacency-changes {
+ detail
+ }
+ parameters {
+ abr-type cisco
+ router-id 172.16.254.30
+ }
+ passive-interface default
+ passive-interface-exclude eth0.5
+ redistribute {
+ connected {
+ metric-type 2
+ route-map MAP-OSPF-CONNECTED
+ }
+ }
+ }
+ static {
+ interface-route6 2000::/3 {
+ next-hop-interface pppoe0 {
+ }
+ }
+ route 10.0.0.0/8 {
+ blackhole {
+ distance 254
+ }
+ }
+ route 169.254.0.0/16 {
+ blackhole {
+ distance 254
+ }
+ }
+ route 172.16.0.0/12 {
+ blackhole {
+ distance 254
+ }
+ }
+ route 172.16.32.0/21 {
+ blackhole {
+ }
+ }
+ route 172.18.0.0/16 {
+ blackhole {
+ }
+ }
+ route 172.29.0.2/31 {
+ next-hop 172.29.0.0 {
+ }
+ }
+ route 192.168.0.0/16 {
+ blackhole {
+ distance 254
+ }
+ }
+ route 192.168.189.0/24 {
+ next-hop 172.29.0.0 {
+ }
+ }
+ }
+}
+service {
+ dhcp-server {
+ shared-network-name BACKBONE {
+ authoritative
+ subnet 172.16.37.0/24 {
+ default-router 172.16.37.254
+ domain-name vyos.net
+ domain-search vyos.net
+ lease 86400
+ name-server 172.16.254.30
+ ntp-server 172.16.254.30
+ range 0 {
+ start 172.16.37.120
+ stop 172.16.37.149
+ }
+ static-mapping AP1 {
+ ip-address 172.16.37.231
+ mac-address 02:00:00:00:ee:18
+ }
+ static-mapping AP2 {
+ ip-address 172.16.37.232
+ mac-address 02:00:00:00:52:84
+ }
+ static-mapping AP3 {
+ ip-address 172.16.37.233
+ mac-address 02:00:00:00:51:c0
+ }
+ static-mapping AP4 {
+ ip-address 172.16.37.234
+ mac-address 02:00:00:00:e6:fc
+ }
+ static-mapping AP5 {
+ ip-address 172.16.37.235
+ mac-address 02:00:00:00:c3:50
+ }
+ }
+ }
+ shared-network-name GUEST {
+ authoritative
+ subnet 172.31.0.0/24 {
+ default-router 172.31.0.254
+ domain-name vyos.net
+ domain-search vyos.net
+ lease 86400
+ name-server 172.31.0.254
+ range 0 {
+ start 172.31.0.101
+ stop 172.31.0.199
+ }
+ }
+ }
+ shared-network-name LAN {
+ authoritative
+ subnet 172.16.33.0/24 {
+ default-router 172.16.33.254
+ domain-name vyos.net
+ domain-search vyos.net
+ lease 86400
+ name-server 172.16.254.30
+ ntp-server 172.16.254.30
+ range 0 {
+ start 172.16.33.100
+ stop 172.16.33.189
+ }
+ static-mapping one {
+ ip-address 172.16.33.221
+ mac-address 02:00:00:00:eb:a6
+ }
+ static-mapping two {
+ ip-address 172.16.33.211
+ mac-address 02:00:00:00:58:90
+ }
+ static-mapping three {
+ ip-address 172.16.33.212
+ mac-address 02:00:00:00:12:c7
+ }
+ static-mapping four {
+ ip-address 172.16.33.214
+ mac-address 02:00:00:00:c4:33
+ }
+ }
+ }
+ }
+ dns {
+ dynamic {
+ interface pppoe0 {
+ service vyos {
+ host-name r1.vyos.net
+ login vyos-vyos
+ password vyos
+ protocol dyndns2
+ server dyndns.vyos.io
+ }
+ }
+ }
+ forwarding {
+ allow-from 172.16.0.0/12
+ domain 16.172.in-addr.arpa {
+ addnta
+ recursion-desired
+ server 172.16.100.10
+ server 172.16.100.20
+ }
+ domain 18.172.in-addr.arpa {
+ addnta
+ recursion-desired
+ server 172.16.100.10
+ server 172.16.100.20
+ }
+ domain vyos.net {
+ addnta
+ recursion-desired
+ server 172.16.100.20
+ server 172.16.100.10
+ }
+ ignore-hosts-file
+ listen-address 172.16.254.30
+ listen-address 172.31.0.254
+ negative-ttl 60
+ }
+ }
+ lldp {
+ legacy-protocols {
+ cdp
+ edp
+ fdp
+ sonmp
+ }
+ snmp {
+ enable
+ }
+ }
+ router-advert {
+ interface eth0.10 {
+ prefix ::/64 {
+ preferred-lifetime 2700
+ valid-lifetime 5400
+ }
+ }
+ interface eth1.20 {
+ prefix ::/64 {
+ preferred-lifetime 2700
+ valid-lifetime 5400
+ }
+ }
+ }
+ snmp {
+ community ro-community {
+ authorization ro
+ network 172.16.100.0/24
+ }
+ contact "VyOS"
+ listen-address 172.16.254.30 {
+ port 161
+ }
+ location "CLOUD"
+ }
+ ssh {
+ disable-host-validation
+ port 22
+ }
+}
+system {
+ config-management {
+ commit-revisions 200
+ }
+ conntrack {
+ expect-table-size 2048
+ hash-size 32768
+ modules {
+ ftp
+ h323
+ nfs
+ pptp
+ sqlnet
+ tftp
+ }
+ table-size 262144
+ timeout {
+ icmp 30
+ other 600
+ udp {
+ other 300
+ stream 300
+ }
+ }
+ }
+ console {
+ device ttyS0 {
+ speed 115200
+ }
+ }
+ domain-name vyos.net
+ host-name r1
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/
+ plaintext-password ""
+ }
+ }
+ }
+ name-server 172.16.254.30
+ ntp {
+ allow-clients {
+ address 172.16.0.0/12
+ }
+ server time1.vyos.net {
+ }
+ server time2.vyos.net {
+ }
+ }
+ option {
+ ctrl-alt-delete ignore
+ performance latency
+ reboot-on-panic
+ startup-beep
+ }
+ syslog {
+ global {
+ facility all {
+ level debug
+ }
+ facility protocols {
+ level debug
+ }
+ }
+ host 172.16.100.1 {
+ facility all {
+ level warning
+ }
+ }
+ }
+ time-zone Europe/Berlin
+}
+traffic-policy {
+ shaper QoS {
+ bandwidth 50mbit
+ default {
+ bandwidth 100%
+ burst 15k
+ queue-limit 1000
+ queue-type fq-codel
+ }
+ }
+}
+zone-policy {
+ zone DMZ {
+ default-action drop
+ from GUEST {
+ firewall {
+ name GUEST-DMZ
+ }
+ }
+ from LAN {
+ firewall {
+ name LAN-DMZ
+ }
+ }
+ from LOCAL {
+ firewall {
+ name LOCAL-DMZ
+ }
+ }
+ from WAN {
+ firewall {
+ name WAN-DMZ
+ }
+ }
+ interface eth0.50
+ }
+ zone GUEST {
+ default-action drop
+ from DMZ {
+ firewall {
+ name DMZ-GUEST
+ }
+ }
+ from LAN {
+ firewall {
+ name LAN-GUEST
+ }
+ }
+ from LOCAL {
+ firewall {
+ ipv6-name ALLOW-ALL-6
+ name LOCAL-GUEST
+ }
+ }
+ from WAN {
+ firewall {
+ ipv6-name ALLOW-ESTABLISHED-6
+ name WAN-GUEST
+ }
+ }
+ interface eth1.20
+ }
+ zone LAN {
+ default-action drop
+ from DMZ {
+ firewall {
+ name DMZ-LAN
+ }
+ }
+ from GUEST {
+ firewall {
+ name GUEST-LAN
+ }
+ }
+ from LOCAL {
+ firewall {
+ ipv6-name ALLOW-ALL-6
+ name LOCAL-LAN
+ }
+ }
+ from WAN {
+ firewall {
+ ipv6-name ALLOW-ESTABLISHED-6
+ name WAN-LAN
+ }
+ }
+ interface eth0.5
+ interface eth0.10
+ interface wg100
+ interface wg200
+ }
+ zone LOCAL {
+ default-action drop
+ from DMZ {
+ firewall {
+ name DMZ-LOCAL
+ }
+ }
+ from GUEST {
+ firewall {
+ ipv6-name ALLOW-ESTABLISHED-6
+ name GUEST-LOCAL
+ }
+ }
+ from LAN {
+ firewall {
+ ipv6-name ALLOW-ALL-6
+ name LAN-LOCAL
+ }
+ }
+ from WAN {
+ firewall {
+ ipv6-name WAN-LOCAL-6
+ name WAN-LOCAL
+ }
+ }
+ local-zone
+ }
+ zone WAN {
+ default-action drop
+ from DMZ {
+ firewall {
+ name DMZ-WAN
+ }
+ }
+ from GUEST {
+ firewall {
+ ipv6-name ALLOW-ALL-6
+ name GUEST-WAN
+ }
+ }
+ from LAN {
+ firewall {
+ ipv6-name ALLOW-ALL-6
+ name LAN-WAN
+ }
+ }
+ from LOCAL {
+ firewall {
+ ipv6-name ALLOW-ALL-6
+ name LOCAL-WAN
+ }
+ }
+ interface pppoe0
+ interface wg666
+ }
+}
+
+
+// Warning: Do not remove the following line.
+// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1"
+// Release version: 1.3.4
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index 71e2142f9..97dab255e 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -1133,5 +1133,20 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' mpls bgp forwarding', frrconfig)
self.cli_delete(['interfaces', 'ethernet', interface, 'vrf'])
+ def test_bgp_24_srv6_sid(self):
+ locator_name = 'VyOS_foo'
+ sid = 'auto'
+
+ self.cli_set(base_path + ['srv6', 'locator', locator_name])
+ self.cli_set(base_path + ['sid', 'vpn', 'per-vrf', 'export', sid])
+
+ self.cli_commit()
+
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ self.assertIn(f'router bgp {ASN}', frrconfig)
+ self.assertIn(f' segment-routing srv6', frrconfig)
+ self.assertIn(f' locator {locator_name}', frrconfig)
+ self.assertIn(f' sid vpn per-vrf export {sid}', frrconfig)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_protocols_segment_routing.py b/smoketest/scripts/cli/test_protocols_segment_routing.py
new file mode 100755
index 000000000..81d42b925
--- /dev/null
+++ b/smoketest/scripts/cli/test_protocols_segment_routing.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSessionError
+from vyos.utils.process import cmd
+from vyos.utils.process import process_named_running
+
+base_path = ['protocols', 'segment-routing']
+PROCESS_NAME = 'zebra'
+
+class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ # call base-classes classmethod
+ super(TestProtocolsSegmentRouting, cls).setUpClass()
+ # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
+ cls.daemon_pid = process_named_running(PROCESS_NAME)
+ # 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):
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # check process health and continuity
+ self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+
+ def test_srv6(self):
+ locators = {
+ 'foo' : { 'prefix' : '2001:a::/64' },
+ 'foo' : { 'prefix' : '2001:b::/64', 'usid' : {} },
+ }
+
+ for locator, locator_config in locators.items():
+ self.cli_set(base_path + ['srv6', 'locator', locator, 'prefix', locator_config['prefix']])
+ if 'usid' in locator_config:
+ self.cli_set(base_path + ['srv6', 'locator', locator, 'behavior-usid'])
+
+ self.cli_commit()
+
+ frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra')
+ self.assertIn(f'segment-routing', frrconfig)
+ self.assertIn(f' srv6', frrconfig)
+ self.assertIn(f' locators', frrconfig)
+ for locator, locator_config in locators.items():
+ self.assertIn(f' locator {locator}', frrconfig)
+ self.assertIn(f' prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig)
+
+
+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 093e43494..9f6e05ff3 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -14,11 +14,15 @@
# 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 json import loads
+
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
+from vyos.utils.dict import dict_search_recursive
from vyos.utils.process import process_named_running
from vyos.utils.file import read_file
from vyos.template import address_from_cidr
@@ -26,8 +30,10 @@ from vyos.template import inc_ip
from vyos.template import dec_ip
from vyos.template import netmask_from_cidr
-PROCESS_NAME = 'dhcpd'
-DHCPD_CONF = '/run/dhcp-server/dhcpd.conf'
+PROCESS_NAME = 'kea-dhcp4'
+CTRL_PROCESS_NAME = 'kea-ctrl-agent'
+KEA4_CONF = '/run/kea/kea-dhcp4.conf'
+KEA4_CTRL = '/run/kea/dhcp4-ctrl-socket'
base_path = ['service', 'dhcp-server']
subnet = '192.0.2.0/25'
router = inc_ip(subnet, 1)
@@ -52,6 +58,36 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ def walk_path(self, obj, path):
+ current = obj
+
+ for i, key in enumerate(path):
+ if isinstance(key, str):
+ self.assertTrue(isinstance(current, dict), msg=f'Failed path: {path}')
+ self.assertTrue(key in current, msg=f'Failed path: {path}')
+ elif isinstance(key, int):
+ self.assertTrue(isinstance(current, list), msg=f'Failed path: {path}')
+ self.assertTrue(0 <= key < len(current), msg=f'Failed path: {path}')
+ else:
+ assert False, "Invalid type"
+
+ current = current[key]
+
+ return current
+
+ def verify_config_object(self, obj, path, value):
+ base_obj = self.walk_path(obj, path)
+ self.assertTrue(isinstance(base_obj, list))
+ self.assertTrue(any(True for v in base_obj if v == value))
+
+ def verify_config_value(self, obj, path, key, value):
+ base_obj = self.walk_path(obj, path)
+ if isinstance(base_obj, list):
+ self.assertTrue(any(True for v in base_obj if key in v and v[key] == value))
+ elif isinstance(base_obj, dict):
+ self.assertTrue(key in base_obj)
+ self.assertEqual(base_obj[key], value)
+
def test_dhcp_single_pool_range(self):
shared_net_name = 'SMOKE-1'
@@ -60,15 +96,12 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
range_1_start = inc_ip(subnet, 40)
range_1_stop = inc_ip(subnet, 50)
- self.cli_set(base_path + ['dynamic-dns-update'])
-
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
# we use the first subnet IP address as default gateway
self.cli_set(pool + ['default-router', router])
self.cli_set(pool + ['name-server', dns_1])
self.cli_set(pool + ['name-server', dns_2])
self.cli_set(pool + ['domain-name', domain_name])
- self.cli_set(pool + ['ping-check'])
# check validate() - No DHCP address range or active static-mapping set
with self.assertRaises(ConfigSessionError):
@@ -81,20 +114,37 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- config = read_file(DHCPD_CONF)
- network = address_from_cidr(subnet)
- netmask = netmask_from_cidr(subnet)
- self.assertIn(f'ddns-update-style interim;', config)
- self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config)
- self.assertIn(f'option domain-name-servers {dns_1}, {dns_2};', config)
- self.assertIn(f'option routers {router};', config)
- self.assertIn(f'option domain-name "{domain_name}";', config)
- self.assertIn(f'default-lease-time 86400;', config)
- self.assertIn(f'max-lease-time 86400;', config)
- self.assertIn(f'ping-check true;', config)
- self.assertIn(f'range {range_0_start} {range_0_stop};', config)
- self.assertIn(f'range {range_1_start} {range_1_stop};', config)
- self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
+ config = read_file(KEA4_CONF)
+ obj = loads(config)
+
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400)
+
+ # Verify options
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name', 'data': domain_name})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router})
+
+ # Verify pools
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_1_start} - {range_1_stop}'})
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -134,6 +184,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.cli_set(pool + ['static-route', '10.0.0.0/24', 'next-hop', '192.0.2.1'])
self.cli_set(pool + ['ipv6-only-preferred', ipv6_only_preferred])
+ self.cli_set(pool + ['time-zone', 'Europe/London'])
# check validate() - No DHCP address range or active static-mapping set
with self.assertRaises(ConfigSessionError):
@@ -144,38 +195,89 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- config = read_file(DHCPD_CONF)
-
- network = address_from_cidr(subnet)
- netmask = netmask_from_cidr(subnet)
- self.assertIn(f'ddns-update-style none;', config)
- self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config)
- self.assertIn(f'option domain-name-servers {dns_1}, {dns_2};', config)
- self.assertIn(f'option routers {router};', config)
- self.assertIn(f'option domain-name "{domain_name}";', config)
-
- search = '"' + ('", "').join(search_domains) + '"'
- self.assertIn(f'option domain-search {search};', config)
-
- self.assertIn(f'option ip-forwarding true;', config)
- self.assertIn(f'option smtp-server {smtp_server};', config)
- self.assertIn(f'option pop-server {smtp_server};', config)
- self.assertIn(f'option time-servers {time_server};', config)
- self.assertIn(f'option wpad-url "{wpad}";', config)
- self.assertIn(f'option dhcp-server-identifier {server_identifier};', config)
- self.assertIn(f'option tftp-server-name "{tftp_server}";', config)
- self.assertIn(f'option bootfile-name "{bootfile_name}";', config)
- self.assertIn(f'filename "{bootfile_name}";', config)
- self.assertIn(f'next-server {bootfile_server};', config)
- self.assertIn(f'default-lease-time 86400;', config)
- self.assertIn(f'max-lease-time 86400;', config)
- self.assertIn(f'range {range_0_start} {range_0_stop};', config)
- self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
- self.assertIn(f'option rfc8925-ipv6-only-preferred {ipv6_only_preferred};', config)
-
- # weird syntax for those static routes
- self.assertIn(f'option rfc3442-static-route 24,10,0,0,192,0,2,1, 0,192,0,2,1;', config)
- self.assertIn(f'option windows-static-route 24,10,0,0,192,0,2,1;', config)
+ config = read_file(KEA4_CONF)
+ obj = loads(config)
+
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'boot-file-name', bootfile_name)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'next-server', bootfile_server)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400)
+
+ # Verify options
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name', 'data': domain_name})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-search', 'data': ', '.join(search_domains)})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'pop-server', 'data': smtp_server})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'smtp-server', 'data': smtp_server})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'time-servers', 'data': time_server})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'dhcp-server-identifier', 'data': server_identifier})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'tftp-server-name', 'data': tftp_server})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'wpad-url', 'data': wpad})
+ 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'})
+ 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'})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'v6-only-preferred', 'data': ipv6_only_preferred})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'ip-forwarding', 'data': "true"})
+
+ # Time zone
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'pcode', 'data': 'GMT0BST,M3.5.0/1,M10.5.0'})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'tcode', 'data': 'Europe/London'})
+
+ # Verify pools
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'})
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -205,27 +307,39 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- config = read_file(DHCPD_CONF)
- network = address_from_cidr(subnet)
- netmask = netmask_from_cidr(subnet)
- self.assertIn(f'ddns-update-style none;', config)
- self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config)
- self.assertIn(f'option domain-name-servers {dns_1}, {dns_2};', config)
- self.assertIn(f'option routers {router};', config)
- self.assertIn(f'option domain-name "{domain_name}";', config)
- self.assertIn(f'default-lease-time 86400;', config)
- self.assertIn(f'max-lease-time 86400;', config)
+ config = read_file(KEA4_CONF)
+ obj = loads(config)
+
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400)
+
+ # Verify options
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name', 'data': domain_name})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router})
client_base = 10
for client in ['client1', 'client2', 'client3']:
mac = '00:50:00:00:00:{}'.format(client_base)
ip = inc_ip(subnet, client_base)
- self.assertIn(f'host {shared_net_name}_{client}' + ' {', config)
- self.assertIn(f'fixed-address {ip};', config)
- self.assertIn(f'hardware ethernet {mac};', config)
- client_base += 1
- self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'reservations'],
+ {'hw-address': mac, 'ip-address': ip})
+
+ client_base += 1
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -266,7 +380,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- config = read_file(DHCPD_CONF)
+ config = read_file(KEA4_CONF)
+ obj = loads(config)
+
for network in ['0', '1', '2', '3']:
shared_net_name = f'VyOS-SMOKETEST-{network}'
subnet = f'192.0.{network}.0/24'
@@ -278,27 +394,43 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
range_1_start = inc_ip(subnet, 30)
range_1_stop = inc_ip(subnet, 40)
- network = address_from_cidr(subnet)
- netmask = netmask_from_cidr(subnet)
-
- self.assertIn(f'ddns-update-style none;', config)
- self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config)
- self.assertIn(f'option domain-name-servers {dns_1};', config)
- self.assertIn(f'option routers {router};', config)
- self.assertIn(f'option domain-name "{domain_name}";', config)
- self.assertIn(f'default-lease-time {lease_time};', config)
- self.assertIn(f'max-lease-time {lease_time};', config)
- self.assertIn(f'range {range_0_start} {range_0_stop};', config)
- self.assertIn(f'range {range_1_start} {range_1_stop};', config)
- self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'valid-lifetime', int(lease_time))
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'max-valid-lifetime', int(lease_time))
+
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name', 'data': domain_name})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name-servers', 'data': dns_1})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router})
+
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'})
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'pools'],
+ {'pool': f'{range_1_start} - {range_1_stop}'})
client_base = 60
for client in ['client1', 'client2', 'client3', 'client4']:
mac = '02:50:00:00:00:{}'.format(client_base)
ip = inc_ip(subnet, client_base)
- self.assertIn(f'host {shared_net_name}_{client}' + ' {', config)
- self.assertIn(f'fixed-address {ip};', config)
- self.assertIn(f'hardware ethernet {mac};', config)
+
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'reservations'],
+ {'hw-address': mac, 'ip-address': ip})
+
client_base += 1
# Check for running process
@@ -319,14 +451,23 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- # VErify
- config = read_file(DHCPD_CONF)
- network = address_from_cidr(subnet)
- netmask = netmask_from_cidr(subnet)
+ config = read_file(KEA4_CONF)
+ obj = loads(config)
- self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config)
- self.assertIn(f'option routers {router};', config)
- self.assertIn(f'range {range_0_start} {range_0_stop};', config)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST')
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+
+ # Verify options
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router})
+
+ # Verify pools
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'})
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -352,15 +493,27 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- # Verify
- config = read_file(DHCPD_CONF)
- network = address_from_cidr(subnet)
- netmask = netmask_from_cidr(subnet)
+ config = read_file(KEA4_CONF)
+ obj = loads(config)
+
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST-2')
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
- self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config)
- self.assertIn(f'option routers {router};', config)
- self.assertIn(f'range {range_0_start} {range_0_stop_excl};', config)
- self.assertIn(f'range {range_0_start_excl} {range_0_stop};', config)
+ # Verify options
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router})
+
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop_excl}'})
+
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start_excl} - {range_0_stop}'})
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -384,41 +537,23 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- config = read_file(DHCPD_CONF)
- network = address_from_cidr(subnet)
- netmask = netmask_from_cidr(subnet)
- # Check the relay network
- self.assertIn(f'subnet {network} netmask {netmask}' + r' { }', config)
-
- relay_network = address_from_cidr(relay_subnet)
- relay_netmask = netmask_from_cidr(relay_subnet)
- self.assertIn(f'subnet {relay_network} netmask {relay_netmask}' + r' {', config)
- self.assertIn(f'option routers {relay_router};', config)
- self.assertIn(f'range {range_0_start} {range_0_stop};', config)
-
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
-
- def test_dhcp_invalid_raw_options(self):
- shared_net_name = 'SMOKE-5'
+ config = read_file(KEA4_CONF)
+ obj = loads(config)
- range_0_start = inc_ip(subnet, 10)
- range_0_stop = inc_ip(subnet, 20)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'RELAY')
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', relay_subnet)
- pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
- # we use the first subnet IP address as default gateway
- self.cli_set(pool + ['default-router', router])
- self.cli_set(pool + ['range', '0', 'start', range_0_start])
- self.cli_set(pool + ['range', '0', 'stop', range_0_stop])
+ # Verify options
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': relay_router})
- self.cli_set(base_path + ['global-parameters', 'this-is-crap'])
- # check generate() - dhcpd should not acceot this garbage config
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
- self.cli_delete(base_path + ['global-parameters'])
-
- # commit changes
- self.cli_commit()
+ # Verify pools
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'})
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -449,41 +584,43 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['failover', 'remote', failover_remote])
self.cli_set(base_path + ['failover', 'status', 'primary'])
- # check validate() - failover needs to be enabled for at least one subnet
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
- self.cli_set(pool + ['enable-failover'])
-
# commit changes
self.cli_commit()
- config = read_file(DHCPD_CONF)
-
- self.assertIn(f'failover peer "{failover_name}"' + r' {', config)
- self.assertIn(f'primary;', config)
- self.assertIn(f'mclt 1800;', config)
- self.assertIn(f'mclt 1800;', config)
- self.assertIn(f'split 128;', config)
- self.assertIn(f'port 647;', config)
- self.assertIn(f'peer port 647;', config)
- self.assertIn(f'max-response-delay 30;', config)
- self.assertIn(f'max-unacked-updates 10;', config)
- self.assertIn(f'load balance max seconds 3;', config)
- self.assertIn(f'address {failover_local};', config)
- self.assertIn(f'peer address {failover_remote};', config)
-
- network = address_from_cidr(subnet)
- netmask = netmask_from_cidr(subnet)
- self.assertIn(f'ddns-update-style none;', config)
- self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config)
- self.assertIn(f'option routers {router};', config)
- self.assertIn(f'range {range_0_start} {range_0_stop};', config)
- self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
- self.assertIn(f'failover peer "{failover_name}";', config)
- self.assertIn(f'deny dynamic bootp clients;', config)
+ config = read_file(KEA4_CONF)
+ obj = loads(config)
+
+ # Verify failover
+ self.verify_config_value(obj, ['Dhcp4', 'control-socket'], 'socket-name', KEA4_CTRL)
+
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'],
+ {'name': os.uname()[1], 'url': f'http://{failover_local}:647/', 'role': 'primary', 'auto-failover': True})
+
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'],
+ {'name': failover_name, 'url': f'http://{failover_remote}:647/', 'role': 'standby', 'auto-failover': True})
+
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+
+ # Verify options
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router})
+
+ # Verify pools
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'})
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
+ self.assertTrue(process_named_running(CTRL_PROCESS_NAME))
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py
index 4d9dabc3f..175a67537 100755
--- a/smoketest/scripts/cli/test_service_dhcpv6-server.py
+++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2022 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -16,6 +16,8 @@
import unittest
+from json import loads
+
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
@@ -23,8 +25,8 @@ from vyos.template import inc_ip
from vyos.utils.process import process_named_running
from vyos.utils.file import read_file
-PROCESS_NAME = 'dhcpd'
-DHCPD_CONF = '/run/dhcp-server/dhcpdv6.conf'
+PROCESS_NAME = 'kea-dhcp6'
+KEA6_CONF = '/run/kea/kea-dhcp6.conf'
base_path = ['service', 'dhcpv6-server']
subnet = '2001:db8:f00::/64'
@@ -52,6 +54,36 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
+ def walk_path(self, obj, path):
+ current = obj
+
+ for i, key in enumerate(path):
+ if isinstance(key, str):
+ self.assertTrue(isinstance(current, dict), msg=f'Failed path: {path}')
+ self.assertTrue(key in current, msg=f'Failed path: {path}')
+ elif isinstance(key, int):
+ self.assertTrue(isinstance(current, list), msg=f'Failed path: {path}')
+ self.assertTrue(0 <= key < len(current), msg=f'Failed path: {path}')
+ else:
+ assert False, "Invalid type"
+
+ current = current[key]
+
+ return current
+
+ def verify_config_object(self, obj, path, value):
+ base_obj = self.walk_path(obj, path)
+ self.assertTrue(isinstance(base_obj, list))
+ self.assertTrue(any(True for v in base_obj if v == value))
+
+ def verify_config_value(self, obj, path, key, value):
+ base_obj = self.walk_path(obj, path)
+ if isinstance(base_obj, list):
+ self.assertTrue(any(True for v in base_obj if key in v and v[key] == value))
+ elif isinstance(base_obj, dict):
+ self.assertTrue(key in base_obj)
+ self.assertEqual(base_obj[key], value)
+
def test_single_pool(self):
shared_net_name = 'SMOKE-1'
search_domains = ['foo.vyos.net', 'bar.vyos.net']
@@ -99,34 +131,66 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- config = read_file(DHCPD_CONF)
- self.assertIn(f'option dhcp6.preference {preference};', config)
-
- self.assertIn(f'subnet6 {subnet}' + r' {', config)
- search = '"' + '", "'.join(search_domains) + '"'
- nissrv = ', '.join(nis_servers)
- self.assertIn(f'range6 {range_start} {range_stop};', config)
- self.assertIn(f'default-lease-time {lease_time};', config)
- self.assertIn(f'default-lease-time {lease_time};', config)
- self.assertIn(f'max-lease-time {max_lease_time};', config)
- self.assertIn(f'min-lease-time {min_lease_time};', config)
- self.assertIn(f'option dhcp6.domain-search {search};', config)
- self.assertIn(f'option dhcp6.name-servers {dns_1}, {dns_2};', config)
- self.assertIn(f'option dhcp6.nis-domain-name "{domain}";', config)
- self.assertIn(f'option dhcp6.nis-servers {nissrv};', config)
- self.assertIn(f'option dhcp6.nisp-domain-name "{domain}";', config)
- self.assertIn(f'option dhcp6.nisp-servers {nissrv};', config)
- self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
+ config = read_file(KEA6_CONF)
+ obj = loads(config)
+
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name)
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'valid-lifetime', int(lease_time))
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'min-valid-lifetime', int(min_lease_time))
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'max-valid-lifetime', int(max_lease_time))
+
+ # Verify options
+ 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,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'domain-search', 'data': ", ".join(search_domains)})
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'nis-domain-name', 'data': domain})
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'nis-servers', 'data': ", ".join(nis_servers)})
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'nisp-domain-name', 'data': domain})
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'nisp-servers', 'data': ", ".join(nis_servers)})
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'sntp-servers', 'data': sntp_server})
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'sip-server-dns', 'data': sip_server})
+
+ # Verify pools
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'pools'],
+ {'pool': f'{range_start} - {range_stop}'})
client_base = 1
for client in ['client1', 'client2', 'client3']:
cid = '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{}'.format(client_base)
ip = inc_ip(subnet, client_base)
prefix = inc_ip(subnet, client_base << 64) + '/64'
- self.assertIn(f'host {shared_net_name}_{client}' + ' {', config)
- self.assertIn(f'fixed-address6 {ip};', config)
- self.assertIn(f'fixed-prefix6 {prefix};', config)
- self.assertIn(f'host-identifier option dhcp6.client-id {cid};', config)
+
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'reservations'],
+ {'duid': cid, 'ip-addresses': [ip], 'prefixes': [prefix]})
+
client_base += 1
# Check for running process
@@ -138,22 +202,34 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
range_start = inc_ip(subnet, 256) # ::100
range_stop = inc_ip(subnet, 65535) # ::ffff
delegate_start = '2001:db8:ee::'
- delegate_stop = '2001:db8:ee:ff00::'
- delegate_len = '56'
+ delegate_len = '64'
+ prefix_len = '56'
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
self.cli_set(pool + ['address-range', 'start', range_start, 'stop', range_stop])
- self.cli_set(pool + ['prefix-delegation', 'start', delegate_start, 'stop', delegate_stop])
- self.cli_set(pool + ['prefix-delegation', 'start', delegate_start, 'prefix-length', delegate_len])
+ self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'delegated-length', delegate_len])
+ self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'prefix-length', prefix_len])
# commit changes
self.cli_commit()
- config = read_file(DHCPD_CONF)
- self.assertIn(f'subnet6 {subnet}' + r' {', config)
- self.assertIn(f'range6 {range_start} {range_stop};', config)
- self.assertIn(f'prefix6 {delegate_start} {delegate_stop} /{delegate_len};', config)
+ config = read_file(KEA6_CONF)
+ obj = loads(config)
+
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name)
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet)
+
+ # Verify pools
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'pools'],
+ {'pool': f'{range_start} - {range_stop}'})
+
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'pd-pools'],
+ {'prefix': delegate_start, 'prefix-len': int(prefix_len), 'delegated-len': int(delegate_len)})
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
@@ -170,10 +246,16 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- config = read_file(DHCPD_CONF)
- self.assertIn(f'option dhcp6.name-servers {ns_global_1}, {ns_global_2};', config)
- self.assertIn(f'subnet6 {subnet}' + r' {', config)
- self.assertIn(f'set shared-networkname = "{shared_net_name}";', config)
+ config = read_file(KEA6_CONF)
+ obj = loads(config)
+
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name)
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet)
+
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'option-data'],
+ {'name': 'dns-servers', "code": 23, "space": "dhcp6", "csv-format": True, 'data': f'{ns_global_1}, {ns_global_2}'})
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py
index cb3d90593..ae46b18ba 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -17,8 +17,6 @@
import os
import unittest
import tempfile
-import random
-import string
from base_vyostest_shim import VyOSUnitTestSHIM
@@ -67,14 +65,12 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
self.cli_set(name_path + [svc, 'address', interface])
self.cli_set(name_path + [svc, 'host-name', hostname])
self.cli_set(name_path + [svc, 'password', password])
- self.cli_set(name_path + [svc, 'zone', zone])
- self.cli_set(name_path + [svc, 'ttl', ttl])
for opt, value in details.items():
self.cli_set(name_path + [svc, opt, value])
- # 'zone' option is supported and required by 'cloudfare', but not 'freedns' and 'zoneedit'
+ # 'zone' option is supported by 'cloudfare' and 'zoneedit1', but not 'freedns'
self.cli_set(name_path + [svc, 'zone', zone])
- if details['protocol'] == 'cloudflare':
+ if details['protocol'] in ['cloudflare', 'zoneedit1']:
pass
else:
# exception is raised for unsupported ones
@@ -292,9 +288,38 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'password=\'{password}\'', ddclient_conf)
self.assertIn(f'{hostname}', ddclient_conf)
- def test_07_dyndns_vrf(self):
+ def test_07_dyndns_dynamic_interface(self):
+ # Check if DDNS service can be configured and runs
+ svc_path = name_path + ['namecheap']
+ proto = 'namecheap'
+ dyn_interface = 'pppoe587'
+
+ self.cli_set(svc_path + ['address', dyn_interface])
+ self.cli_set(svc_path + ['protocol', proto])
+ self.cli_set(svc_path + ['server', server])
+ self.cli_set(svc_path + ['username', username])
+ self.cli_set(svc_path + ['password', password])
+ self.cli_set(svc_path + ['host-name', hostname])
+
+ # Dynamic interface will raise a warning but still go through
+ # XXX: We should have idiomatic class "ConfigSessionWarning" wrapping
+ # "Warning" similar to "ConfigSessionError".
+ # with self.assertWarns(Warning):
+ # self.cli_commit()
+ self.cli_commit()
+
+ # Check the generating config parameters
+ ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}')
+ self.assertIn(f'ifv4={dyn_interface}', ddclient_conf)
+ self.assertIn(f'protocol={proto}', ddclient_conf)
+ self.assertIn(f'server={server}', ddclient_conf)
+ self.assertIn(f'login={username}', ddclient_conf)
+ self.assertIn(f'password=\'{password}\'', ddclient_conf)
+ self.assertIn(f'{hostname}', ddclient_conf)
+
+ def test_08_dyndns_vrf(self):
# Table number randomized, but should be within range 100-65535
- vrf_table = "".join(random.choices(string.digits, k=4))
+ vrf_table = '58710'
vrf_name = f'vyos-test-{vrf_table}'
svc_path = name_path + ['cloudflare']
proto = 'cloudflare'
diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py
index 6cb91bcf1..703e3e8c4 100755
--- a/smoketest/scripts/cli/test_service_https.py
+++ b/smoketest/scripts/cli/test_service_https.py
@@ -23,6 +23,7 @@ from urllib3.exceptions import InsecureRequestWarning
from base_vyostest_shim import VyOSUnitTestSHIM
from base_vyostest_shim import ignore_warning
from vyos.utils.file import read_file
+from vyos.utils.process import call
from vyos.utils.process import process_named_running
from vyos.configsession import ConfigSessionError
@@ -51,6 +52,23 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx
u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww
"""
+# to test load config via HTTP URL
+nginx_conf_smoketest = """
+server {
+ listen 8000;
+ server_name localhost;
+
+ root /tmp;
+
+ index index.html;
+
+ location / {
+ try_files $uri $uri/ =404;
+ autoindex on;
+ }
+}
+"""
+
PROCESS_NAME = 'nginx'
class TestHTTPSService(VyOSUnitTestSHIM.TestCase):
@@ -375,6 +393,57 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase):
r = request('POST', url, verify=False, headers=headers, data=payload)
self.assertEqual(r.status_code, 200)
+ @ignore_warning(InsecureRequestWarning)
+ def test_api_config_file_load_http(self):
+ """Test load config from HTTP URL
+ """
+ address = '127.0.0.1'
+ key = 'VyOS-key'
+ url = f'https://{address}/config-file'
+ url_config = f'https://{address}/configure'
+ headers = {}
+ tmp_file = 'tmp-config.boot'
+ nginx_tmp_site = '/etc/nginx/sites-enabled/smoketest'
+
+ self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key])
+ self.cli_commit()
+
+ # load config via HTTP requires nginx config
+ call(f'sudo touch {nginx_tmp_site}')
+ call(f'sudo chown vyos:vyattacfg {nginx_tmp_site}')
+ call(f'sudo chmod +w {nginx_tmp_site}')
+
+ with open(nginx_tmp_site, 'w') as f:
+ f.write(nginx_conf_smoketest)
+ call('sudo nginx -s reload')
+
+ # save config
+ payload = {
+ 'data': '{"op": "save", "file": "/tmp/tmp-config.boot"}',
+ 'key': f'{key}',
+ }
+ r = request('POST', url, verify=False, headers=headers, data=payload)
+ self.assertEqual(r.status_code, 200)
+
+ # change config
+ payload = {
+ 'data': '{"op": "set", "path": ["interfaces", "dummy", "dum1", "address", "192.0.2.31/32"]}',
+ 'key': f'{key}',
+ }
+ r = request('POST', url_config, verify=False, headers=headers, data=payload)
+ self.assertEqual(r.status_code, 200)
+
+ # load config from URL
+ payload = {
+ 'data': '{"op": "load", "file": "http://localhost:8000/tmp-config.boot"}',
+ 'key': f'{key}',
+ }
+ r = request('POST', url, verify=False, headers=headers, data=payload)
+ self.assertEqual(r.status_code, 200)
+
+ # cleanup tmp nginx conf
+ call(f'sudo rm -rf {nginx_tmp_site}')
+ call('sudo nginx -s reload')
if __name__ == '__main__':
unittest.main(verbosity=5)
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index ac7d95632..66f7c8057 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -21,10 +21,16 @@ from ipaddress import ip_network
from netaddr import IPAddress
from netaddr import IPRange
from sys import exit
+from time import sleep
from vyos.config import Config
+from vyos.pki import wrap_certificate
+from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.utils.dict import dict_search
+from vyos.utils.dict import dict_search_args
+from vyos.utils.file import chmod_775
+from vyos.utils.file import write_file
from vyos.utils.process import call
from vyos.utils.process import run
from vyos.utils.network import is_subnet_connected
@@ -33,8 +39,14 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
-config_file = '/run/dhcp-server/dhcpd.conf'
-systemd_override = r'/run/systemd/system/isc-dhcp-server.service.d/10-override.conf'
+ctrl_config_file = '/run/kea/kea-ctrl-agent.conf'
+ctrl_socket = '/run/kea/dhcp4-ctrl-socket'
+config_file = '/run/kea/kea-dhcp4.conf'
+lease_file = '/config/dhcp4.leases'
+
+ca_cert_file = '/run/kea/kea-failover-ca.pem'
+cert_file = '/run/kea/kea-failover.pem'
+cert_key_file = '/run/kea/kea-failover-key.pem'
def dhcp_slice_range(exclude_list, range_dict):
"""
@@ -130,6 +142,9 @@ def get_config(config=None):
dhcp['shared_network_name'][network]['subnet'][subnet].update(
{'range' : new_range_dict})
+ if dict_search('failover.certificate', dhcp):
+ dhcp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
+
return dhcp
def verify(dhcp):
@@ -166,13 +181,6 @@ def verify(dhcp):
if 'next_hop' not in route_option:
raise ConfigError(f'DHCP static-route "{route}" requires router to be defined!')
- # DHCP failover needs at least one subnet that uses it
- if 'enable_failover' in subnet_config:
- if 'failover' not in dhcp:
- raise ConfigError(f'Can not enable failover for "{subnet}" in "{network}".\n' \
- 'Failover is not configured globally!')
- failover_ok = True
-
# Check if DHCP address range is inside configured subnet declaration
if 'range' in subnet_config:
networks = []
@@ -249,14 +257,34 @@ def verify(dhcp):
raise ConfigError(f'At least one shared network must be active!')
if 'failover' in dhcp:
- if not failover_ok:
- raise ConfigError('DHCP failover must be enabled for at least one subnet!')
-
for key in ['name', 'remote', 'source_address', 'status']:
if key not in dhcp['failover']:
tmp = key.replace('_', '-')
raise ConfigError(f'DHCP failover requires "{tmp}" to be specified!')
+ if len({'certificate', 'ca_certificate'} & set(dhcp['failover'])) == 1:
+ raise ConfigError(f'DHCP secured failover requires both certificate and CA certificate')
+
+ if 'certificate' in dhcp['failover']:
+ cert_name = dhcp['failover']['certificate']
+
+ if cert_name not in dhcp['pki']['certificate']:
+ raise ConfigError(f'Invalid certificate specified for DHCP failover')
+
+ if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'certificate'):
+ raise ConfigError(f'Invalid certificate specified for DHCP failover')
+
+ if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'private', 'key'):
+ raise ConfigError(f'Missing private key on certificate specified for DHCP failover')
+
+ if 'ca_certificate' in dhcp['failover']:
+ ca_cert_name = dhcp['failover']['ca_certificate']
+ if ca_cert_name not in dhcp['pki']['ca']:
+ raise ConfigError(f'Invalid CA certificate specified for DHCP failover')
+
+ if not dict_search_args(dhcp['pki']['ca'], ca_cert_name, 'certificate'):
+ raise ConfigError(f'Invalid CA certificate specified for DHCP failover')
+
for address in (dict_search('listen_address', dhcp) or []):
if is_addr_assigned(address):
listen_ok = True
@@ -278,43 +306,71 @@ def generate(dhcp):
if not dhcp or 'disable' in dhcp:
return None
- # Please see: https://vyos.dev/T1129 for quoting of the raw
- # parameters we can pass to ISC DHCPd
- tmp_file = '/tmp/dhcpd.conf'
- render(tmp_file, 'dhcp-server/dhcpd.conf.j2', dhcp,
- formater=lambda _: _.replace("&quot;", '"'))
- # XXX: as we have the ability for a user to pass in "raw" options via VyOS
- # CLI (see T3544) we now ask ISC dhcpd to test the newly rendered
- # configuration
- tmp = run(f'/usr/sbin/dhcpd -4 -q -t -cf {tmp_file}')
- if tmp > 0:
- if os.path.exists(tmp_file):
- os.unlink(tmp_file)
- raise ConfigError('Configuration file errors encountered - check your options!')
-
- # Now that we know that the newly rendered configuration is "good" we can
- # render the "real" configuration
- render(config_file, 'dhcp-server/dhcpd.conf.j2', dhcp,
- formater=lambda _: _.replace("&quot;", '"'))
- render(systemd_override, 'dhcp-server/10-override.conf.j2', dhcp)
-
- # Clean up configuration test file
- if os.path.exists(tmp_file):
- os.unlink(tmp_file)
+ dhcp['lease_file'] = lease_file
+ dhcp['machine'] = os.uname().machine
+
+ if not os.path.exists(lease_file):
+ write_file(lease_file, '', user='_kea', group='vyattacfg', mode=0o755)
+
+ for f in [cert_file, cert_key_file, ca_cert_file]:
+ if os.path.exists(f):
+ os.unlink(f)
+
+ if 'failover' in dhcp:
+ if 'certificate' in dhcp['failover']:
+ cert_name = dhcp['failover']['certificate']
+ cert_data = dhcp['pki']['certificate'][cert_name]['certificate']
+ key_data = dhcp['pki']['certificate'][cert_name]['private']['key']
+ write_file(cert_file, wrap_certificate(cert_data), user='_kea', mode=0o600)
+ write_file(cert_key_file, wrap_private_key(key_data), user='_kea', mode=0o600)
+
+ dhcp['failover']['cert_file'] = cert_file
+ dhcp['failover']['cert_key_file'] = cert_key_file
+
+ if 'ca_certificate' in dhcp['failover']:
+ ca_cert_name = dhcp['failover']['ca_certificate']
+ ca_cert_data = dhcp['pki']['ca'][ca_cert_name]['certificate']
+ write_file(ca_cert_file, wrap_certificate(ca_cert_data), user='_kea', mode=0o600)
+
+ dhcp['failover']['ca_cert_file'] = ca_cert_file
+
+ render(ctrl_config_file, 'dhcp-server/kea-ctrl-agent.conf.j2', dhcp)
+ render(config_file, 'dhcp-server/kea-dhcp4.conf.j2', dhcp)
return None
def apply(dhcp):
- call('systemctl daemon-reload')
- # bail out early - looks like removal from running config
+ services = ['kea-ctrl-agent', 'kea-dhcp4-server', 'kea-dhcp-ddns-server']
+
if not dhcp or 'disable' in dhcp:
- call('systemctl stop isc-dhcp-server.service')
+ for service in services:
+ call(f'systemctl stop {service}.service')
+
if os.path.exists(config_file):
os.unlink(config_file)
return None
- call('systemctl restart isc-dhcp-server.service')
+ for service in services:
+ action = 'restart'
+
+ if service == 'kea-dhcp-ddns-server' and 'dynamic_dns_update' not in dhcp:
+ action = 'stop'
+
+ if service == 'kea-ctrl-agent' and 'failover' not in dhcp:
+ action = 'stop'
+
+ call(f'systemctl {action} {service}.service')
+
+ # op-mode needs ctrl socket permission change
+ i = 0
+ while not os.path.exists(ctrl_socket):
+ if i > 15:
+ break
+ i += 1
+ sleep(1)
+ chmod_775(ctrl_socket)
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py
index 427001609..73a708ff5 100755
--- a/src/conf_mode/dhcpv6_server.py
+++ b/src/conf_mode/dhcpv6_server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2022 VyOS maintainers and contributors
+# 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
@@ -19,18 +19,23 @@ import os
from ipaddress import ip_address
from ipaddress import ip_network
from sys import exit
+from time import sleep
from vyos.config import Config
from vyos.template import render
from vyos.template import is_ipv6
from vyos.utils.process import call
+from vyos.utils.file import chmod_775
+from vyos.utils.file import write_file
from vyos.utils.dict import dict_search
from vyos.utils.network import is_subnet_connected
from vyos import ConfigError
from vyos import airbag
airbag.enable()
-config_file = '/run/dhcp-server/dhcpdv6.conf'
+config_file = '/run/kea/kea-dhcp6.conf'
+ctrl_socket = '/run/kea/dhcp6-ctrl-socket'
+lease_file = '/config/dhcp6.leases'
def get_config(config=None):
if config:
@@ -110,17 +115,20 @@ def verify(dhcpv6):
# Prefix delegation sanity checks
if 'prefix_delegation' in subnet_config:
- if 'start' not in subnet_config['prefix_delegation']:
- raise ConfigError('prefix-delegation start address not defined!')
+ if 'prefix' not in subnet_config['prefix_delegation']:
+ raise ConfigError('prefix-delegation prefix not defined!')
- for prefix, prefix_config in subnet_config['prefix_delegation']['start'].items():
- if 'stop' not in prefix_config:
- raise ConfigError(f'Stop address of delegated IPv6 prefix range "{prefix}" '\
+ for prefix, prefix_config in subnet_config['prefix_delegation']['prefix'].items():
+ if 'delegated_length' not in prefix_config:
+ raise ConfigError(f'Delegated IPv6 prefix length for "{prefix}" '\
f'must be configured')
if 'prefix_length' not in prefix_config:
raise ConfigError('Length of delegated IPv6 prefix must be configured')
+ if prefix_config['prefix_length'] > prefix_config['delegated_length']:
+ raise ConfigError('Length of delegated IPv6 prefix must be within parent prefix')
+
# Static mappings don't require anything (but check if IP is in subnet if it's set)
if 'static_mapping' in subnet_config:
for mapping, mapping_config in subnet_config['static_mapping'].items():
@@ -168,12 +176,18 @@ def generate(dhcpv6):
if not dhcpv6 or 'disable' in dhcpv6:
return None
- render(config_file, 'dhcp-server/dhcpdv6.conf.j2', dhcpv6)
+ dhcpv6['lease_file'] = lease_file
+ dhcpv6['machine'] = os.uname().machine
+
+ if not os.path.exists(lease_file):
+ write_file(lease_file, '', user='_kea', group='vyattacfg', mode=0o755)
+
+ render(config_file, 'dhcp-server/kea-dhcp6.conf.j2', dhcpv6)
return None
def apply(dhcpv6):
# bail out early - looks like removal from running config
- service_name = 'isc-dhcp-server6.service'
+ service_name = 'kea-dhcp6-server.service'
if not dhcpv6 or 'disable' in dhcpv6:
# DHCP server is removed in the commit
call(f'systemctl stop {service_name}')
@@ -182,6 +196,16 @@ def apply(dhcpv6):
return None
call(f'systemctl restart {service_name}')
+
+ # op-mode needs ctrl socket permission change
+ i = 0
+ while not os.path.exists(ctrl_socket):
+ if i > 15:
+ break
+ i += 1
+ sleep(1)
+ chmod_775(ctrl_socket)
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py
index 3ddc8e7fd..809c650d9 100755
--- a/src/conf_mode/dns_dynamic.py
+++ b/src/conf_mode/dns_dynamic.py
@@ -18,6 +18,7 @@ import os
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configverify import verify_interface_exists
from vyos.template import render
@@ -29,6 +30,9 @@ airbag.enable()
config_file = r'/run/ddclient/ddclient.conf'
systemd_override = r'/run/systemd/system/ddclient.service.d/override.conf'
+# Dynamic interfaces that might not exist when the configuration is loaded
+dynamic_interfaces = ('pppoe', 'sstpc')
+
# Protocols that require zone
zone_necessary = ['cloudflare', 'digitalocean', 'godaddy', 'hetzner', 'gandi',
'nfsn', 'nsupdate']
@@ -85,12 +89,19 @@ def verify(dyndns):
if field not in config:
raise ConfigError(f'"{field.replace("_", "-")}" {error_msg_req}')
- # If dyndns address is an interface, ensure that it exists
+ # If dyndns address is an interface, ensure
+ # that the interface exists (or just warn if dynamic interface)
# and that web-options are not set
if config['address'] != 'web':
- verify_interface_exists(config['address'])
+ # exclude check interface for dynamic interfaces
+ if config['address'].startswith(dynamic_interfaces):
+ Warning(f'Interface "{config["address"]}" does not exist yet and cannot '
+ f'be used for Dynamic DNS service "{service}" until it is up!')
+ else:
+ verify_interface_exists(config['address'])
if 'web_options' in config:
- raise ConfigError(f'"web-options" is applicable only when using HTTP(S) web request to obtain the IP address')
+ raise ConfigError(f'"web-options" is applicable only when using HTTP(S) '
+ f'web request to obtain the IP address')
# RFC2136 uses 'key' instead of 'password'
if config['protocol'] != 'nsupdate' and 'password' not in config:
@@ -118,13 +129,16 @@ def verify(dyndns):
if config['ip_version'] == 'both':
if config['protocol'] not in dualstack_supported:
- raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} with protocol "{config["protocol"]}"')
+ raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} '
+ f'with protocol "{config["protocol"]}"')
# dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org)
if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] not in dyndns_dualstack_servers:
- raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} for "{config["server"]}" with protocol "{config["protocol"]}"')
+ raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} '
+ f'for "{config["server"]}" with protocol "{config["protocol"]}"')
if {'wait_time', 'expiry_time'} <= config.keys() and int(config['expiry_time']) < int(config['wait_time']):
- raise ConfigError(f'"expiry-time" must be greater than "wait-time" for Dynamic DNS service "{service}"')
+ raise ConfigError(f'"expiry-time" must be greater than "wait-time" for '
+ f'Dynamic DNS service "{service}"')
return None
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 00015023c..557f0a9e9 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -93,6 +93,7 @@ def get_config(config=None):
tmp = conf.get_config_dict(['policy'])
# Merge policy dict into "regular" config dict
bgp = dict_merge(tmp, bgp)
+
return bgp
diff --git a/src/conf_mode/protocols_segment_routing.py b/src/conf_mode/protocols_segment_routing.py
new file mode 100755
index 000000000..eb1653212
--- /dev/null
+++ b/src/conf_mode/protocols_segment_routing.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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_to_string
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['protocols', 'segment-routing']
+ sr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
+
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ sr = conf.merge_defaults(sr, recursive=True)
+
+ return sr
+
+def verify(static):
+ return None
+
+def generate(static):
+ if not static:
+ return None
+
+ static['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', static)
+ return None
+
+def apply(static):
+ zebra_daemon = 'zebra'
+
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+ frr_cfg.load_configuration(zebra_daemon)
+ frr_cfg.modify_section(r'^segment-routing')
+ if 'new_frr_config' in static:
+ frr_cfg.add_before(frr.default_add_before, static['new_frr_config'])
+ frr_cfg.commit_configuration(zebra_daemon)
+
+ 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-login.py b/src/conf_mode/system-login.py
index 87a269499..aeac82462 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -306,6 +306,7 @@ def generate(login):
def apply(login):
+ enable_otp = False
if 'user' in login:
for user, user_config in login['user'].items():
# make new user using vyatta shell and make home directory (-m),
@@ -330,7 +331,7 @@ def apply(login):
if tmp: command += f" --home '{tmp}'"
else: command += f" --home '/home/{user}'"
- command += f' --groups frr,frrvty,vyattacfg,sudo,adm,dip,disk {user}'
+ command += f' --groups frr,frrvty,vyattacfg,sudo,adm,dip,disk,_kea {user}'
try:
cmd(command)
@@ -350,6 +351,7 @@ def apply(login):
# Generate 2FA/MFA One-Time-Pad configuration
if dict_search('authentication.otp.key', user_config):
+ enable_otp = True
render(f'{home_dir}/.google_authenticator', 'login/pam_otp_ga.conf.j2',
user_config, permission=0o400, user=user, group='users')
else:
@@ -398,6 +400,11 @@ def apply(login):
pam_profile = 'tacplus-optional'
cmd(f'pam-auth-update --enable {pam_profile}')
+ # Enable/disable Google authenticator
+ cmd('pam-auth-update --disable mfa-google-authenticator')
+ if enable_otp:
+ cmd(f'pam-auth-update --enable mfa-google-authenticator')
+
return None
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
new file mode 100644
index 000000000..0f5bf801e
--- /dev/null
+++ b/src/etc/systemd/system/kea-ctrl-agent.service.d/override.conf
@@ -0,0 +1,9 @@
+[Unit]
+After=
+After=vyos-router.service
+
+[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-dhcp4-server.service.d/override.conf b/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf
new file mode 100644
index 000000000..682e5bbce
--- /dev/null
+++ b/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf
@@ -0,0 +1,7 @@
+[Unit]
+After=
+After=vyos-router.service
+
+[Service]
+ExecStart=
+ExecStart=/usr/sbin/kea-dhcp4 -c /run/kea/kea-dhcp4.conf
diff --git a/src/etc/systemd/system/kea-dhcp6-server.service.d/override.conf b/src/etc/systemd/system/kea-dhcp6-server.service.d/override.conf
new file mode 100644
index 000000000..cb33fc057
--- /dev/null
+++ b/src/etc/systemd/system/kea-dhcp6-server.service.d/override.conf
@@ -0,0 +1,7 @@
+[Unit]
+After=
+After=vyos-router.service
+
+[Service]
+ExecStart=
+ExecStart=/usr/sbin/kea-dhcp6 -c /run/kea/kea-dhcp6.conf
diff --git a/src/helpers/simple-download.py b/src/helpers/simple-download.py
new file mode 100755
index 000000000..501af75f5
--- /dev/null
+++ b/src/helpers/simple-download.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+
+import sys
+from argparse import ArgumentParser
+from vyos.remote import download
+
+parser = ArgumentParser()
+parser.add_argument('--local-file', help='local file', required=True)
+parser.add_argument('--remote-path', help='remote path', required=True)
+
+args = parser.parse_args()
+
+try:
+ download(args.local_file, args.remote_path,
+ check_space=True, raise_error=True)
+except Exception as e:
+ print(e)
+ sys.exit(1)
+
+sys.exit()
diff --git a/src/init/vyos-router b/src/init/vyos-router
index 35095afe4..711681a8e 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -260,6 +260,8 @@ EOF
rm -f /etc/pam_radius_auth.conf
pam-auth-update --disable tacplus-mandatory tacplus-optional
rm -f /etc/tacplus_nss.conf /etc/tacplus_servers
+ # and no Google authenticator for 2FA/MFA
+ pam-auth-update --disable mfa-google-authenticator
# Certain configuration files are re-generated by the configuration
# subsystem and must reside under /etc and can not easily be moved to /run.
diff --git a/src/migration-scripts/dhcp-server/6-to-7 b/src/migration-scripts/dhcp-server/6-to-7
new file mode 100755
index 000000000..ccf385a30
--- /dev/null
+++ b/src/migration-scripts/dhcp-server/6-to-7
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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/>.
+
+# T3316: Migrate to Kea
+# - global-parameters will not function
+# - shared-network-parameters will not function
+# - subnet-parameters will not function
+# - static-mapping-parameters will not function
+# - host-decl-name is on by default, option removed
+# - ping-check no longer supported
+# - failover is default enabled on all subnets that exist on failover servers
+
+import sys
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 2):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['service', 'dhcp-server']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+
+if config.exists(base + ['host-decl-name']):
+ config.delete(base + ['host-decl-name'])
+
+if config.exists(base + ['global-parameters']):
+ config.delete(base + ['global-parameters'])
+
+if config.exists(base + ['shared-network-name']):
+ 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'])
+
+ if not config.exists(base_network + ['subnet']):
+ continue
+
+ # Run this for every specified 'subnet'
+ for subnet in config.list_nodes(base_network + ['subnet']):
+ base_subnet = base_network + ['subnet', subnet]
+
+ 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'])
+
+ if config.exists(base_subnet + ['static-mapping']):
+ for mapping in config.list_nodes(base_subnet + ['static-mapping']):
+ if config.exists(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']):
+ config.delete(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/dhcpv6-server/1-to-2 b/src/migration-scripts/dhcpv6-server/1-to-2
new file mode 100755
index 000000000..cc5a8900a
--- /dev/null
+++ b/src/migration-scripts/dhcpv6-server/1-to-2
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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/>.
+
+# T3316: Migrate to Kea
+# - Kea was meant to have support for key "prefix-highest" under PD which would allow an address range
+# However this seems to have never been implemented. A conversion to prefix length is needed (where possible).
+# Ref: https://lists.isc.org/pipermail/kea-users/2022-November/003686.html
+# - Remove prefix temporary value, convert to multi leafNode (https://kea.readthedocs.io/en/kea-2.2.0/arm/dhcp6-srv.html#dhcpv6-server-limitations)
+
+import sys
+from vyos.configtree import ConfigTree
+from vyos.utils.network import ipv6_prefix_length
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['service', 'dhcpv6-server', 'shared-network-name']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+for network in config.list_nodes(base):
+ if not config.exists(base + [network, 'subnet']):
+ continue
+
+ for subnet in config.list_nodes(base + [network, 'subnet']):
+ # Delete temporary value under address-range prefix, convert tagNode to leafNode multi
+ if config.exists(base + [network, 'subnet', subnet, 'address-range', 'prefix']):
+ prefix_base = base + [network, 'subnet', subnet, 'address-range', 'prefix']
+ prefixes = config.list_nodes(prefix_base)
+
+ config.delete(prefix_base)
+
+ for prefix in prefixes:
+ config.set(prefix_base, value=prefix, replace=False)
+
+ if config.exists(base + [network, 'subnet', subnet, 'prefix-delegation', 'prefix']):
+ prefix_base = base + [network, 'subnet', subnet, 'prefix-delegation', 'prefix']
+
+ config.set(prefix_base)
+ config.set_tag(prefix_base)
+
+ for start in config.list_nodes(base + [network, 'subnet', subnet, 'prefix-delegation', 'start']):
+ path = base + [network, 'subnet', subnet, 'prefix-delegation', 'start', start]
+
+ delegated_length = config.return_value(path + ['prefix-length'])
+ stop = config.return_value(path + ['stop'])
+
+ prefix_length = ipv6_prefix_length(start, stop)
+
+ # This range could not be converted into a simple prefix length and must be skipped
+ if not prefix_length:
+ continue
+
+ config.set(prefix_base + [start, 'delegated-length'], value=delegated_length)
+ config.set(prefix_base + [start, 'prefix-length'], value=prefix_length)
+
+ config.delete(base + [network, 'subnet', subnet, 'prefix-delegation', 'start'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/interfaces/22-to-23 b/src/migration-scripts/interfaces/22-to-23
index 8b21fce51..04e023e77 100755
--- a/src/migration-scripts/interfaces/22-to-23
+++ b/src/migration-scripts/interfaces/22-to-23
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-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
@@ -13,133 +13,45 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-from sys import argv
-from sys import exit
+#
+# Deletes Wireguard peers if they have the same public key as the router has.
+import sys
from vyos.configtree import ConfigTree
-
-def migrate_ospf(config, path, interface):
- path = path + ['ospf']
- if config.exists(path):
- new_base = ['protocols', 'ospf', 'interface']
- config.set(new_base)
- config.set_tag(new_base)
- config.copy(path, new_base + [interface])
- config.delete(path)
-
- # if "ip ospf" was the only setting, we can clean out the empty
- # ip node afterwards
- if len(config.list_nodes(path[:-1])) == 0:
- config.delete(path[:-1])
-
-def migrate_ospfv3(config, path, interface):
- path = path + ['ospfv3']
- if config.exists(path):
- new_base = ['protocols', 'ospfv3', 'interface']
- config.set(new_base)
- config.set_tag(new_base)
- config.copy(path, new_base + [interface])
- config.delete(path)
-
- # if "ipv6 ospfv3" was the only setting, we can clean out the empty
- # ip node afterwards
- if len(config.list_nodes(path[:-1])) == 0:
- config.delete(path[:-1])
-
-def migrate_rip(config, path, interface):
- path = path + ['rip']
- if config.exists(path):
- new_base = ['protocols', 'rip', 'interface']
- config.set(new_base)
- config.set_tag(new_base)
- config.copy(path, new_base + [interface])
- config.delete(path)
-
- # if "ip rip" was the only setting, we can clean out the empty
- # ip node afterwards
- if len(config.list_nodes(path[:-1])) == 0:
- config.delete(path[:-1])
-
-def migrate_ripng(config, path, interface):
- path = path + ['ripng']
- if config.exists(path):
- new_base = ['protocols', 'ripng', 'interface']
- config.set(new_base)
- config.set_tag(new_base)
- config.copy(path, new_base + [interface])
- config.delete(path)
-
- # if "ipv6 ripng" was the only setting, we can clean out the empty
- # ip node afterwards
- if len(config.list_nodes(path[:-1])) == 0:
- config.delete(path[:-1])
+from vyos.utils.network import is_wireguard_key_pair
if __name__ == '__main__':
- if len(argv) < 2:
+ if len(sys.argv) < 2:
print("Must specify file name!")
- exit(1)
+ sys.exit(1)
+
+ file_name = sys.argv[1]
- file_name = argv[1]
with open(file_name, 'r') as f:
config_file = f.read()
config = ConfigTree(config_file)
-
- #
- # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0"
- #
- for type in config.list_nodes(['interfaces']):
- for interface in config.list_nodes(['interfaces', type]):
- ip_base = ['interfaces', type, interface, 'ip']
- ipv6_base = ['interfaces', type, interface, 'ipv6']
- migrate_rip(config, ip_base, interface)
- migrate_ripng(config, ipv6_base, interface)
- migrate_ospf(config, ip_base, interface)
- migrate_ospfv3(config, ipv6_base, interface)
-
- vif_path = ['interfaces', type, interface, 'vif']
- if config.exists(vif_path):
- for vif in config.list_nodes(vif_path):
- vif_ip_base = vif_path + [vif, 'ip']
- vif_ipv6_base = vif_path + [vif, 'ipv6']
- ifname = f'{interface}.{vif}'
-
- migrate_rip(config, vif_ip_base, ifname)
- migrate_ripng(config, vif_ipv6_base, ifname)
- migrate_ospf(config, vif_ip_base, ifname)
- migrate_ospfv3(config, vif_ipv6_base, ifname)
-
-
- vif_s_path = ['interfaces', type, interface, 'vif-s']
- if config.exists(vif_s_path):
- for vif_s in config.list_nodes(vif_s_path):
- vif_s_ip_base = vif_s_path + [vif_s, 'ip']
- vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6']
-
- # vif-c interfaces MUST be migrated before their parent vif-s
- # interface as the migrate_*() functions delete the path!
- vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c']
- if config.exists(vif_c_path):
- for vif_c in config.list_nodes(vif_c_path):
- vif_c_ip_base = vif_c_path + [vif_c, 'ip']
- vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6']
- ifname = f'{interface}.{vif_s}.{vif_c}'
-
- migrate_rip(config, vif_c_ip_base, ifname)
- migrate_ripng(config, vif_c_ipv6_base, ifname)
- migrate_ospf(config, vif_c_ip_base, ifname)
- migrate_ospfv3(config, vif_c_ipv6_base, ifname)
-
-
- ifname = f'{interface}.{vif_s}'
- migrate_rip(config, vif_s_ip_base, ifname)
- migrate_ripng(config, vif_s_ipv6_base, ifname)
- migrate_ospf(config, vif_s_ip_base, ifname)
- migrate_ospfv3(config, vif_s_ipv6_base, ifname)
+ base = ['interfaces', 'wireguard']
+ if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+ for interface in config.list_nodes(base):
+ if not config.exists(base + [interface, 'private-key']):
+ continue
+ private_key = config.return_value(base + [interface, 'private-key'])
+ interface_base = base + [interface]
+ if config.exists(interface_base + ['peer']):
+ for peer in config.list_nodes(interface_base + ['peer']):
+ peer_base = interface_base + ['peer', peer]
+ if not config.exists(peer_base + ['public-key']):
+ continue
+ peer_public_key = config.return_value(peer_base + ['public-key'])
+ if not config.exists(peer_base + ['disable']) \
+ and is_wireguard_key_pair(private_key, peer_public_key):
+ config.set(peer_base + ['disable'])
try:
with open(file_name, 'w') as f:
f.write(config.to_string())
except OSError as e:
print("Failed to save the modified config: {}".format(e))
- exit(1)
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/23-to-24 b/src/migration-scripts/interfaces/23-to-24
index 8fd79ecc6..8b21fce51 100755
--- a/src/migration-scripts/interfaces/23-to-24
+++ b/src/migration-scripts/interfaces/23-to-24
@@ -14,47 +14,132 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-# A VTI interface also requires an IPSec configuration - VyOS 1.2 supported
-# having a VTI interface in the CLI but no IPSec configuration - drop VTI
-# configuration if this is the case for VyOS 1.4
-
-import sys
+from sys import argv
+from sys import exit
from vyos.configtree import ConfigTree
+def migrate_ospf(config, path, interface):
+ path = path + ['ospf']
+ if config.exists(path):
+ new_base = ['protocols', 'ospf', 'interface']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.copy(path, new_base + [interface])
+ config.delete(path)
+
+ # if "ip ospf" was the only setting, we can clean out the empty
+ # ip node afterwards
+ if len(config.list_nodes(path[:-1])) == 0:
+ config.delete(path[:-1])
+
+def migrate_ospfv3(config, path, interface):
+ path = path + ['ospfv3']
+ if config.exists(path):
+ new_base = ['protocols', 'ospfv3', 'interface']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.copy(path, new_base + [interface])
+ config.delete(path)
+
+ # if "ipv6 ospfv3" was the only setting, we can clean out the empty
+ # ip node afterwards
+ if len(config.list_nodes(path[:-1])) == 0:
+ config.delete(path[:-1])
+
+def migrate_rip(config, path, interface):
+ path = path + ['rip']
+ if config.exists(path):
+ new_base = ['protocols', 'rip', 'interface']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.copy(path, new_base + [interface])
+ config.delete(path)
+
+ # if "ip rip" was the only setting, we can clean out the empty
+ # ip node afterwards
+ if len(config.list_nodes(path[:-1])) == 0:
+ config.delete(path[:-1])
+
+def migrate_ripng(config, path, interface):
+ path = path + ['ripng']
+ if config.exists(path):
+ new_base = ['protocols', 'ripng', 'interface']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.copy(path, new_base + [interface])
+ config.delete(path)
+
+ # if "ipv6 ripng" was the only setting, we can clean out the empty
+ # ip node afterwards
+ if len(config.list_nodes(path[:-1])) == 0:
+ config.delete(path[:-1])
+
if __name__ == '__main__':
- if len(sys.argv) < 2:
+ if len(argv) < 2:
print("Must specify file name!")
- sys.exit(1)
-
- file_name = sys.argv[1]
+ exit(1)
+ file_name = argv[1]
with open(file_name, 'r') as f:
config_file = f.read()
config = ConfigTree(config_file)
- base = ['interfaces', 'vti']
- if not config.exists(base):
- # Nothing to do
- sys.exit(0)
-
- ipsec_base = ['vpn', 'ipsec', 'site-to-site', 'peer']
- for interface in config.list_nodes(base):
- found = False
- if config.exists(ipsec_base):
- for peer in config.list_nodes(ipsec_base):
- if config.exists(ipsec_base + [peer, 'vti', 'bind']):
- tmp = config.return_value(ipsec_base + [peer, 'vti', 'bind'])
- if tmp == interface:
- # Interface was found and we no longer need to search
- # for it in our IPSec peers
- found = True
- break
- if not found:
- config.delete(base + [interface])
+
+ #
+ # Migrate "interface ethernet eth0 ip ospf" to "protocols ospf interface eth0"
+ #
+ for type in config.list_nodes(['interfaces']):
+ for interface in config.list_nodes(['interfaces', type]):
+ ip_base = ['interfaces', type, interface, 'ip']
+ ipv6_base = ['interfaces', type, interface, 'ipv6']
+ migrate_rip(config, ip_base, interface)
+ migrate_ripng(config, ipv6_base, interface)
+ migrate_ospf(config, ip_base, interface)
+ migrate_ospfv3(config, ipv6_base, interface)
+
+ vif_path = ['interfaces', type, interface, 'vif']
+ if config.exists(vif_path):
+ for vif in config.list_nodes(vif_path):
+ vif_ip_base = vif_path + [vif, 'ip']
+ vif_ipv6_base = vif_path + [vif, 'ipv6']
+ ifname = f'{interface}.{vif}'
+
+ migrate_rip(config, vif_ip_base, ifname)
+ migrate_ripng(config, vif_ipv6_base, ifname)
+ migrate_ospf(config, vif_ip_base, ifname)
+ migrate_ospfv3(config, vif_ipv6_base, ifname)
+
+
+ vif_s_path = ['interfaces', type, interface, 'vif-s']
+ if config.exists(vif_s_path):
+ for vif_s in config.list_nodes(vif_s_path):
+ vif_s_ip_base = vif_s_path + [vif_s, 'ip']
+ vif_s_ipv6_base = vif_s_path + [vif_s, 'ipv6']
+
+ # vif-c interfaces MUST be migrated before their parent vif-s
+ # interface as the migrate_*() functions delete the path!
+ vif_c_path = ['interfaces', type, interface, 'vif-s', vif_s, 'vif-c']
+ if config.exists(vif_c_path):
+ for vif_c in config.list_nodes(vif_c_path):
+ vif_c_ip_base = vif_c_path + [vif_c, 'ip']
+ vif_c_ipv6_base = vif_c_path + [vif_c, 'ipv6']
+ ifname = f'{interface}.{vif_s}.{vif_c}'
+
+ migrate_rip(config, vif_c_ip_base, ifname)
+ migrate_ripng(config, vif_c_ipv6_base, ifname)
+ migrate_ospf(config, vif_c_ip_base, ifname)
+ migrate_ospfv3(config, vif_c_ipv6_base, ifname)
+
+
+ ifname = f'{interface}.{vif_s}'
+ migrate_rip(config, vif_s_ip_base, ifname)
+ migrate_ripng(config, vif_s_ipv6_base, ifname)
+ migrate_ospf(config, vif_s_ip_base, ifname)
+ migrate_ospfv3(config, vif_s_ipv6_base, ifname)
try:
with open(file_name, 'w') as f:
f.write(config.to_string())
except OSError as e:
print("Failed to save the modified config: {}".format(e))
- sys.exit(1)
+ exit(1)
diff --git a/src/migration-scripts/interfaces/24-to-25 b/src/migration-scripts/interfaces/24-to-25
index 9aa6ea5e3..8fd79ecc6 100755
--- a/src/migration-scripts/interfaces/24-to-25
+++ b/src/migration-scripts/interfaces/24-to-25
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -14,374 +14,47 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-# Migrate Wireguard to store keys in CLI
-# Migrate EAPoL to PKI configuration
+# A VTI interface also requires an IPSec configuration - VyOS 1.2 supported
+# having a VTI interface in the CLI but no IPSec configuration - drop VTI
+# configuration if this is the case for VyOS 1.4
-import os
import sys
-
from vyos.configtree import ConfigTree
-from vyos.pki import CERT_BEGIN
-from vyos.pki import load_certificate
-from vyos.pki import load_crl
-from vyos.pki import load_dh_parameters
-from vyos.pki import load_private_key
-from vyos.pki import encode_certificate
-from vyos.pki import encode_dh_parameters
-from vyos.pki import encode_private_key
-from vyos.pki import verify_crl
-from vyos.utils.process import run
-
-def wrapped_pem_to_config_value(pem):
- out = []
- for line in pem.strip().split("\n"):
- if not line or line.startswith("-----") or line[0] == '#':
- continue
- out.append(line)
- return "".join(out)
-
-def read_file_for_pki(config_auth_path):
- full_path = os.path.join(AUTH_DIR, config_auth_path)
- output = None
-
- if os.path.isfile(full_path):
- if not os.access(full_path, os.R_OK):
- run(f'sudo chmod 644 {full_path}')
-
- with open(full_path, 'r') as f:
- output = f.read()
-
- return output
-if len(sys.argv) < 2:
- print("Must specify file name!")
- sys.exit(1)
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print("Must specify file name!")
+ sys.exit(1)
-file_name = sys.argv[1]
+ file_name = sys.argv[1]
-with open(file_name, 'r') as f:
- config_file = f.read()
+ with open(file_name, 'r') as f:
+ config_file = f.read()
-config = ConfigTree(config_file)
+ config = ConfigTree(config_file)
+ base = ['interfaces', 'vti']
+ if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
-AUTH_DIR = '/config/auth'
-pki_base = ['pki']
-
-# OpenVPN
-base = ['interfaces', 'openvpn']
-
-if config.exists(base):
+ ipsec_base = ['vpn', 'ipsec', 'site-to-site', 'peer']
for interface in config.list_nodes(base):
- x509_base = base + [interface, 'tls']
- pki_name = f'openvpn_{interface}'
-
- if config.exists(base + [interface, 'shared-secret-key-file']):
- if not config.exists(pki_base + ['openvpn', 'shared-secret']):
- config.set(pki_base + ['openvpn', 'shared-secret'])
- config.set_tag(pki_base + ['openvpn', 'shared-secret'])
-
- key_file = config.return_value(base + [interface, 'shared-secret-key-file'])
- key = read_file_for_pki(key_file)
- key_pki_name = f'{pki_name}_shared'
-
- if key:
- config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key))
- config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1')
- config.set(base + [interface, 'shared-secret-key'], value=key_pki_name)
- else:
- print(f'Failed to migrate shared-secret-key on openvpn interface {interface}')
-
- config.delete(base + [interface, 'shared-secret-key-file'])
-
- if not config.exists(base + [interface, 'tls']):
- continue
-
- if config.exists(base + [interface, 'tls', 'auth-file']):
- if not config.exists(pki_base + ['openvpn', 'shared-secret']):
- config.set(pki_base + ['openvpn', 'shared-secret'])
- config.set_tag(pki_base + ['openvpn', 'shared-secret'])
-
- key_file = config.return_value(base + [interface, 'tls', 'auth-file'])
- key = read_file_for_pki(key_file)
- key_pki_name = f'{pki_name}_auth'
-
- if key:
- config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key))
- config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1')
- config.set(base + [interface, 'tls', 'auth-key'], value=key_pki_name)
- else:
- print(f'Failed to migrate auth-key on openvpn interface {interface}')
-
- config.delete(base + [interface, 'tls', 'auth-file'])
-
- if config.exists(base + [interface, 'tls', 'crypt-file']):
- if not config.exists(pki_base + ['openvpn', 'shared-secret']):
- config.set(pki_base + ['openvpn', 'shared-secret'])
- config.set_tag(pki_base + ['openvpn', 'shared-secret'])
-
- key_file = config.return_value(base + [interface, 'tls', 'crypt-file'])
- key = read_file_for_pki(key_file)
- key_pki_name = f'{pki_name}_crypt'
-
- if key:
- config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key))
- config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1')
- config.set(base + [interface, 'tls', 'crypt-key'], value=key_pki_name)
- else:
- print(f'Failed to migrate crypt-key on openvpn interface {interface}')
-
- config.delete(base + [interface, 'tls', 'crypt-file'])
-
- ca_certs = {}
-
- if config.exists(x509_base + ['ca-cert-file']):
- if not config.exists(pki_base + ['ca']):
- config.set(pki_base + ['ca'])
- config.set_tag(pki_base + ['ca'])
-
- cert_file = config.return_value(x509_base + ['ca-cert-file'])
- cert_path = os.path.join(AUTH_DIR, cert_file)
-
- if os.path.isfile(cert_path):
- if not os.access(cert_path, os.R_OK):
- run(f'sudo chmod 644 {cert_path}')
-
- with open(cert_path, 'r') as f:
- certs_str = f.read()
- certs_data = certs_str.split(CERT_BEGIN)
- index = 1
- for cert_data in certs_data[1:]:
- cert = load_certificate(CERT_BEGIN + cert_data, wrap_tags=False)
-
- if cert:
- ca_certs[f'{pki_name}_{index}'] = cert
- cert_pem = encode_certificate(cert)
- config.set(pki_base + ['ca', f'{pki_name}_{index}', 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
- config.set(x509_base + ['ca-certificate'], value=f'{pki_name}_{index}', replace=False)
- else:
- print(f'Failed to migrate CA certificate on openvpn interface {interface}')
-
- index += 1
- else:
- print(f'Failed to migrate CA certificate on openvpn interface {interface}')
-
- config.delete(x509_base + ['ca-cert-file'])
-
- if config.exists(x509_base + ['crl-file']):
- if not config.exists(pki_base + ['ca']):
- config.set(pki_base + ['ca'])
- config.set_tag(pki_base + ['ca'])
-
- crl_file = config.return_value(x509_base + ['crl-file'])
- crl_path = os.path.join(AUTH_DIR, crl_file)
- crl = None
- crl_ca_name = None
-
- if os.path.isfile(crl_path):
- if not os.access(crl_path, os.R_OK):
- run(f'sudo chmod 644 {crl_path}')
-
- with open(crl_path, 'r') as f:
- crl_data = f.read()
- crl = load_crl(crl_data, wrap_tags=False)
-
- for ca_name, ca_cert in ca_certs.items():
- if verify_crl(crl, ca_cert):
- crl_ca_name = ca_name
- break
-
- if crl and crl_ca_name:
- crl_pem = encode_certificate(crl)
- config.set(pki_base + ['ca', crl_ca_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem))
- else:
- print(f'Failed to migrate CRL on openvpn interface {interface}')
-
- config.delete(x509_base + ['crl-file'])
-
- if config.exists(x509_base + ['cert-file']):
- if not config.exists(pki_base + ['certificate']):
- config.set(pki_base + ['certificate'])
- config.set_tag(pki_base + ['certificate'])
-
- cert_file = config.return_value(x509_base + ['cert-file'])
- cert_path = os.path.join(AUTH_DIR, cert_file)
- cert = None
-
- if os.path.isfile(cert_path):
- if not os.access(cert_path, os.R_OK):
- run(f'sudo chmod 644 {cert_path}')
-
- with open(cert_path, 'r') as f:
- cert_data = f.read()
- cert = load_certificate(cert_data, wrap_tags=False)
-
- if cert:
- cert_pem = encode_certificate(cert)
- config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
- config.set(x509_base + ['certificate'], value=pki_name)
- else:
- print(f'Failed to migrate certificate on openvpn interface {interface}')
-
- config.delete(x509_base + ['cert-file'])
-
- if config.exists(x509_base + ['key-file']):
- key_file = config.return_value(x509_base + ['key-file'])
- key_path = os.path.join(AUTH_DIR, key_file)
- key = None
-
- if os.path.isfile(key_path):
- if not os.access(key_path, os.R_OK):
- run(f'sudo chmod 644 {key_path}')
-
- with open(key_path, 'r') as f:
- key_data = f.read()
- key = load_private_key(key_data, passphrase=None, wrap_tags=False)
-
- if key:
- key_pem = encode_private_key(key, passphrase=None)
- config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem))
- else:
- print(f'Failed to migrate private key on openvpn interface {interface}')
-
- config.delete(x509_base + ['key-file'])
-
- if config.exists(x509_base + ['dh-file']):
- if not config.exists(pki_base + ['dh']):
- config.set(pki_base + ['dh'])
- config.set_tag(pki_base + ['dh'])
-
- dh_file = config.return_value(x509_base + ['dh-file'])
- dh_path = os.path.join(AUTH_DIR, dh_file)
- dh = None
-
- if os.path.isfile(dh_path):
- if not os.access(dh_path, os.R_OK):
- run(f'sudo chmod 644 {dh_path}')
-
- with open(dh_path, 'r') as f:
- dh_data = f.read()
- dh = load_dh_parameters(dh_data, wrap_tags=False)
-
- if dh:
- dh_pem = encode_dh_parameters(dh)
- config.set(pki_base + ['dh', pki_name, 'parameters'], value=wrapped_pem_to_config_value(dh_pem))
- config.set(x509_base + ['dh-params'], value=pki_name)
- else:
- print(f'Failed to migrate DH parameters on openvpn interface {interface}')
-
- config.delete(x509_base + ['dh-file'])
-
-# Wireguard
-base = ['interfaces', 'wireguard']
-
-if config.exists(base):
- for interface in config.list_nodes(base):
- private_key_path = base + [interface, 'private-key']
-
- key_file = 'default'
- if config.exists(private_key_path):
- key_file = config.return_value(private_key_path)
-
- full_key_path = f'/config/auth/wireguard/{key_file}/private.key'
-
- if not os.path.exists(full_key_path):
- print(f'Could not find wireguard private key for migration on interface "{interface}"')
- continue
-
- with open(full_key_path, 'r') as f:
- key_data = f.read().strip()
- config.set(private_key_path, value=key_data)
-
- for peer in config.list_nodes(base + [interface, 'peer']):
- config.rename(base + [interface, 'peer', peer, 'pubkey'], 'public-key')
-
-# Ethernet EAPoL
-base = ['interfaces', 'ethernet']
-
-if config.exists(base):
- for interface in config.list_nodes(base):
- if not config.exists(base + [interface, 'eapol']):
- continue
-
- x509_base = base + [interface, 'eapol']
- pki_name = f'eapol_{interface}'
-
- if config.exists(x509_base + ['ca-cert-file']):
- if not config.exists(pki_base + ['ca']):
- config.set(pki_base + ['ca'])
- config.set_tag(pki_base + ['ca'])
-
- cert_file = config.return_value(x509_base + ['ca-cert-file'])
- cert_path = os.path.join(AUTH_DIR, cert_file)
- cert = None
-
- if os.path.isfile(cert_path):
- if not os.access(cert_path, os.R_OK):
- run(f'sudo chmod 644 {cert_path}')
-
- with open(cert_path, 'r') as f:
- cert_data = f.read()
- cert = load_certificate(cert_data, wrap_tags=False)
-
- if cert:
- cert_pem = encode_certificate(cert)
- config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
- config.set(x509_base + ['ca-certificate'], value=pki_name)
- else:
- print(f'Failed to migrate CA certificate on eapol config for interface {interface}')
-
- config.delete(x509_base + ['ca-cert-file'])
-
- if config.exists(x509_base + ['cert-file']):
- if not config.exists(pki_base + ['certificate']):
- config.set(pki_base + ['certificate'])
- config.set_tag(pki_base + ['certificate'])
-
- cert_file = config.return_value(x509_base + ['cert-file'])
- cert_path = os.path.join(AUTH_DIR, cert_file)
- cert = None
-
- if os.path.isfile(cert_path):
- if not os.access(cert_path, os.R_OK):
- run(f'sudo chmod 644 {cert_path}')
-
- with open(cert_path, 'r') as f:
- cert_data = f.read()
- cert = load_certificate(cert_data, wrap_tags=False)
-
- if cert:
- cert_pem = encode_certificate(cert)
- config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
- config.set(x509_base + ['certificate'], value=pki_name)
- else:
- print(f'Failed to migrate certificate on eapol config for interface {interface}')
-
- config.delete(x509_base + ['cert-file'])
-
- if config.exists(x509_base + ['key-file']):
- key_file = config.return_value(x509_base + ['key-file'])
- key_path = os.path.join(AUTH_DIR, key_file)
- key = None
-
- if os.path.isfile(key_path):
- if not os.access(key_path, os.R_OK):
- run(f'sudo chmod 644 {key_path}')
-
- with open(key_path, 'r') as f:
- key_data = f.read()
- key = load_private_key(key_data, passphrase=None, wrap_tags=False)
-
- if key:
- key_pem = encode_private_key(key, passphrase=None)
- config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem))
- else:
- print(f'Failed to migrate private key on eapol config for interface {interface}')
-
- config.delete(x509_base + ['key-file'])
-
-try:
- with open(file_name, 'w') as f:
- f.write(config.to_string())
-except OSError as e:
- print("Failed to save the modified config: {}".format(e))
- sys.exit(1)
+ found = False
+ if config.exists(ipsec_base):
+ for peer in config.list_nodes(ipsec_base):
+ if config.exists(ipsec_base + [peer, 'vti', 'bind']):
+ tmp = config.return_value(ipsec_base + [peer, 'vti', 'bind'])
+ if tmp == interface:
+ # Interface was found and we no longer need to search
+ # for it in our IPSec peers
+ found = True
+ break
+ if not found:
+ config.delete(base + [interface])
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/25-to-26 b/src/migration-scripts/interfaces/25-to-26
index 4967a29fa..9aa6ea5e3 100755
--- a/src/migration-scripts/interfaces/25-to-26
+++ b/src/migration-scripts/interfaces/25-to-26
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2021-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
@@ -14,41 +14,374 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-# T4384: pppoe: replace default-route CLI option with common CLI nodes already
-# present for DHCP
+# Migrate Wireguard to store keys in CLI
+# Migrate EAPoL to PKI configuration
-from sys import argv
+import os
+import sys
-from vyos.ethtool import Ethtool
from vyos.configtree import ConfigTree
+from vyos.pki import CERT_BEGIN
+from vyos.pki import load_certificate
+from vyos.pki import load_crl
+from vyos.pki import load_dh_parameters
+from vyos.pki import load_private_key
+from vyos.pki import encode_certificate
+from vyos.pki import encode_dh_parameters
+from vyos.pki import encode_private_key
+from vyos.pki import verify_crl
+from vyos.utils.process import run
-if len(argv) < 2:
+def wrapped_pem_to_config_value(pem):
+ out = []
+ for line in pem.strip().split("\n"):
+ if not line or line.startswith("-----") or line[0] == '#':
+ continue
+ out.append(line)
+ return "".join(out)
+
+def read_file_for_pki(config_auth_path):
+ full_path = os.path.join(AUTH_DIR, config_auth_path)
+ output = None
+
+ if os.path.isfile(full_path):
+ if not os.access(full_path, os.R_OK):
+ run(f'sudo chmod 644 {full_path}')
+
+ with open(full_path, 'r') as f:
+ output = f.read()
+
+ return output
+
+if len(sys.argv) < 2:
print("Must specify file name!")
- exit(1)
+ sys.exit(1)
+
+file_name = sys.argv[1]
-file_name = argv[1]
with open(file_name, 'r') as f:
config_file = f.read()
-base = ['interfaces', 'pppoe']
config = ConfigTree(config_file)
-if not config.exists(base):
- exit(0)
+AUTH_DIR = '/config/auth'
+pki_base = ['pki']
+
+# OpenVPN
+base = ['interfaces', 'openvpn']
+
+if config.exists(base):
+ for interface in config.list_nodes(base):
+ x509_base = base + [interface, 'tls']
+ pki_name = f'openvpn_{interface}'
+
+ if config.exists(base + [interface, 'shared-secret-key-file']):
+ if not config.exists(pki_base + ['openvpn', 'shared-secret']):
+ config.set(pki_base + ['openvpn', 'shared-secret'])
+ config.set_tag(pki_base + ['openvpn', 'shared-secret'])
+
+ key_file = config.return_value(base + [interface, 'shared-secret-key-file'])
+ key = read_file_for_pki(key_file)
+ key_pki_name = f'{pki_name}_shared'
+
+ if key:
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key))
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1')
+ config.set(base + [interface, 'shared-secret-key'], value=key_pki_name)
+ else:
+ print(f'Failed to migrate shared-secret-key on openvpn interface {interface}')
+
+ config.delete(base + [interface, 'shared-secret-key-file'])
+
+ if not config.exists(base + [interface, 'tls']):
+ continue
+
+ if config.exists(base + [interface, 'tls', 'auth-file']):
+ if not config.exists(pki_base + ['openvpn', 'shared-secret']):
+ config.set(pki_base + ['openvpn', 'shared-secret'])
+ config.set_tag(pki_base + ['openvpn', 'shared-secret'])
+
+ key_file = config.return_value(base + [interface, 'tls', 'auth-file'])
+ key = read_file_for_pki(key_file)
+ key_pki_name = f'{pki_name}_auth'
+
+ if key:
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key))
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1')
+ config.set(base + [interface, 'tls', 'auth-key'], value=key_pki_name)
+ else:
+ print(f'Failed to migrate auth-key on openvpn interface {interface}')
+
+ config.delete(base + [interface, 'tls', 'auth-file'])
+
+ if config.exists(base + [interface, 'tls', 'crypt-file']):
+ if not config.exists(pki_base + ['openvpn', 'shared-secret']):
+ config.set(pki_base + ['openvpn', 'shared-secret'])
+ config.set_tag(pki_base + ['openvpn', 'shared-secret'])
+
+ key_file = config.return_value(base + [interface, 'tls', 'crypt-file'])
+ key = read_file_for_pki(key_file)
+ key_pki_name = f'{pki_name}_crypt'
+
+ if key:
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key))
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1')
+ config.set(base + [interface, 'tls', 'crypt-key'], value=key_pki_name)
+ else:
+ print(f'Failed to migrate crypt-key on openvpn interface {interface}')
+
+ config.delete(base + [interface, 'tls', 'crypt-file'])
+
+ ca_certs = {}
+
+ if config.exists(x509_base + ['ca-cert-file']):
+ if not config.exists(pki_base + ['ca']):
+ config.set(pki_base + ['ca'])
+ config.set_tag(pki_base + ['ca'])
+
+ cert_file = config.return_value(x509_base + ['ca-cert-file'])
+ cert_path = os.path.join(AUTH_DIR, cert_file)
+
+ if os.path.isfile(cert_path):
+ if not os.access(cert_path, os.R_OK):
+ run(f'sudo chmod 644 {cert_path}')
+
+ with open(cert_path, 'r') as f:
+ certs_str = f.read()
+ certs_data = certs_str.split(CERT_BEGIN)
+ index = 1
+ for cert_data in certs_data[1:]:
+ cert = load_certificate(CERT_BEGIN + cert_data, wrap_tags=False)
+
+ if cert:
+ ca_certs[f'{pki_name}_{index}'] = cert
+ cert_pem = encode_certificate(cert)
+ config.set(pki_base + ['ca', f'{pki_name}_{index}', 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
+ config.set(x509_base + ['ca-certificate'], value=f'{pki_name}_{index}', replace=False)
+ else:
+ print(f'Failed to migrate CA certificate on openvpn interface {interface}')
+
+ index += 1
+ else:
+ print(f'Failed to migrate CA certificate on openvpn interface {interface}')
+
+ config.delete(x509_base + ['ca-cert-file'])
+
+ if config.exists(x509_base + ['crl-file']):
+ if not config.exists(pki_base + ['ca']):
+ config.set(pki_base + ['ca'])
+ config.set_tag(pki_base + ['ca'])
+
+ crl_file = config.return_value(x509_base + ['crl-file'])
+ crl_path = os.path.join(AUTH_DIR, crl_file)
+ crl = None
+ crl_ca_name = None
+
+ if os.path.isfile(crl_path):
+ if not os.access(crl_path, os.R_OK):
+ run(f'sudo chmod 644 {crl_path}')
+
+ with open(crl_path, 'r') as f:
+ crl_data = f.read()
+ crl = load_crl(crl_data, wrap_tags=False)
+
+ for ca_name, ca_cert in ca_certs.items():
+ if verify_crl(crl, ca_cert):
+ crl_ca_name = ca_name
+ break
+
+ if crl and crl_ca_name:
+ crl_pem = encode_certificate(crl)
+ config.set(pki_base + ['ca', crl_ca_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem))
+ else:
+ print(f'Failed to migrate CRL on openvpn interface {interface}')
+
+ config.delete(x509_base + ['crl-file'])
+
+ if config.exists(x509_base + ['cert-file']):
+ if not config.exists(pki_base + ['certificate']):
+ config.set(pki_base + ['certificate'])
+ config.set_tag(pki_base + ['certificate'])
+
+ cert_file = config.return_value(x509_base + ['cert-file'])
+ cert_path = os.path.join(AUTH_DIR, cert_file)
+ cert = None
+
+ if os.path.isfile(cert_path):
+ if not os.access(cert_path, os.R_OK):
+ run(f'sudo chmod 644 {cert_path}')
+
+ with open(cert_path, 'r') as f:
+ cert_data = f.read()
+ cert = load_certificate(cert_data, wrap_tags=False)
+
+ if cert:
+ cert_pem = encode_certificate(cert)
+ config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
+ config.set(x509_base + ['certificate'], value=pki_name)
+ else:
+ print(f'Failed to migrate certificate on openvpn interface {interface}')
+
+ config.delete(x509_base + ['cert-file'])
+
+ if config.exists(x509_base + ['key-file']):
+ key_file = config.return_value(x509_base + ['key-file'])
+ key_path = os.path.join(AUTH_DIR, key_file)
+ key = None
+
+ if os.path.isfile(key_path):
+ if not os.access(key_path, os.R_OK):
+ run(f'sudo chmod 644 {key_path}')
+
+ with open(key_path, 'r') as f:
+ key_data = f.read()
+ key = load_private_key(key_data, passphrase=None, wrap_tags=False)
+
+ if key:
+ key_pem = encode_private_key(key, passphrase=None)
+ config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem))
+ else:
+ print(f'Failed to migrate private key on openvpn interface {interface}')
+
+ config.delete(x509_base + ['key-file'])
+
+ if config.exists(x509_base + ['dh-file']):
+ if not config.exists(pki_base + ['dh']):
+ config.set(pki_base + ['dh'])
+ config.set_tag(pki_base + ['dh'])
+
+ dh_file = config.return_value(x509_base + ['dh-file'])
+ dh_path = os.path.join(AUTH_DIR, dh_file)
+ dh = None
+
+ if os.path.isfile(dh_path):
+ if not os.access(dh_path, os.R_OK):
+ run(f'sudo chmod 644 {dh_path}')
+
+ with open(dh_path, 'r') as f:
+ dh_data = f.read()
+ dh = load_dh_parameters(dh_data, wrap_tags=False)
+
+ if dh:
+ dh_pem = encode_dh_parameters(dh)
+ config.set(pki_base + ['dh', pki_name, 'parameters'], value=wrapped_pem_to_config_value(dh_pem))
+ config.set(x509_base + ['dh-params'], value=pki_name)
+ else:
+ print(f'Failed to migrate DH parameters on openvpn interface {interface}')
+
+ config.delete(x509_base + ['dh-file'])
+
+# Wireguard
+base = ['interfaces', 'wireguard']
+
+if config.exists(base):
+ for interface in config.list_nodes(base):
+ private_key_path = base + [interface, 'private-key']
+
+ key_file = 'default'
+ if config.exists(private_key_path):
+ key_file = config.return_value(private_key_path)
+
+ full_key_path = f'/config/auth/wireguard/{key_file}/private.key'
+
+ if not os.path.exists(full_key_path):
+ print(f'Could not find wireguard private key for migration on interface "{interface}"')
+ continue
+
+ with open(full_key_path, 'r') as f:
+ key_data = f.read().strip()
+ config.set(private_key_path, value=key_data)
+
+ for peer in config.list_nodes(base + [interface, 'peer']):
+ config.rename(base + [interface, 'peer', peer, 'pubkey'], 'public-key')
+
+# Ethernet EAPoL
+base = ['interfaces', 'ethernet']
+
+if config.exists(base):
+ for interface in config.list_nodes(base):
+ if not config.exists(base + [interface, 'eapol']):
+ continue
+
+ x509_base = base + [interface, 'eapol']
+ pki_name = f'eapol_{interface}'
+
+ if config.exists(x509_base + ['ca-cert-file']):
+ if not config.exists(pki_base + ['ca']):
+ config.set(pki_base + ['ca'])
+ config.set_tag(pki_base + ['ca'])
+
+ cert_file = config.return_value(x509_base + ['ca-cert-file'])
+ cert_path = os.path.join(AUTH_DIR, cert_file)
+ cert = None
+
+ if os.path.isfile(cert_path):
+ if not os.access(cert_path, os.R_OK):
+ run(f'sudo chmod 644 {cert_path}')
+
+ with open(cert_path, 'r') as f:
+ cert_data = f.read()
+ cert = load_certificate(cert_data, wrap_tags=False)
+
+ if cert:
+ cert_pem = encode_certificate(cert)
+ config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
+ config.set(x509_base + ['ca-certificate'], value=pki_name)
+ else:
+ print(f'Failed to migrate CA certificate on eapol config for interface {interface}')
+
+ config.delete(x509_base + ['ca-cert-file'])
+
+ if config.exists(x509_base + ['cert-file']):
+ if not config.exists(pki_base + ['certificate']):
+ config.set(pki_base + ['certificate'])
+ config.set_tag(pki_base + ['certificate'])
+
+ cert_file = config.return_value(x509_base + ['cert-file'])
+ cert_path = os.path.join(AUTH_DIR, cert_file)
+ cert = None
+
+ if os.path.isfile(cert_path):
+ if not os.access(cert_path, os.R_OK):
+ run(f'sudo chmod 644 {cert_path}')
+
+ with open(cert_path, 'r') as f:
+ cert_data = f.read()
+ cert = load_certificate(cert_data, wrap_tags=False)
+
+ if cert:
+ cert_pem = encode_certificate(cert)
+ config.set(pki_base + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
+ config.set(x509_base + ['certificate'], value=pki_name)
+ else:
+ print(f'Failed to migrate certificate on eapol config for interface {interface}')
+
+ config.delete(x509_base + ['cert-file'])
+
+ if config.exists(x509_base + ['key-file']):
+ key_file = config.return_value(x509_base + ['key-file'])
+ key_path = os.path.join(AUTH_DIR, key_file)
+ key = None
+
+ if os.path.isfile(key_path):
+ if not os.access(key_path, os.R_OK):
+ run(f'sudo chmod 644 {key_path}')
+
+ with open(key_path, 'r') as f:
+ key_data = f.read()
+ key = load_private_key(key_data, passphrase=None, wrap_tags=False)
+
+ if key:
+ key_pem = encode_private_key(key, passphrase=None)
+ config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem))
+ else:
+ print(f'Failed to migrate private key on eapol config for interface {interface}')
-for ifname in config.list_nodes(base):
- tmp_config = base + [ifname, 'default-route']
- if config.exists(tmp_config):
- # Retrieve current config value
- value = config.return_value(tmp_config)
- # Delete old Config node
- config.delete(tmp_config)
- if value == 'none':
- config.set(base + [ifname, 'no-default-route'])
+ config.delete(x509_base + ['key-file'])
try:
with open(file_name, 'w') as f:
f.write(config.to_string())
except OSError as e:
- print(f'Failed to save the modified config: {e}')
- exit(1)
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/26-to-27 b/src/migration-scripts/interfaces/26-to-27
index a0d043d11..4967a29fa 100755
--- a/src/migration-scripts/interfaces/26-to-27
+++ b/src/migration-scripts/interfaces/26-to-27
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2023 VyOS maintainers and contributors
+# 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
@@ -14,8 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-# T4995: pppoe, wwan, sstpc-client rename "authentication user" CLI node
-# to "authentication username"
+# T4384: pppoe: replace default-route CLI option with common CLI nodes already
+# present for DHCP
from sys import argv
@@ -30,16 +30,21 @@ file_name = argv[1]
with open(file_name, 'r') as f:
config_file = f.read()
+base = ['interfaces', 'pppoe']
config = ConfigTree(config_file)
-for type in ['pppoe', 'sstpc-client', 'wwam']:
- base = ['interfaces', type]
- if not config.exists(base):
- continue
- for interface in config.list_nodes(base):
- auth_base = base + [interface, 'authentication', 'user']
- if config.exists(auth_base):
- config.rename(auth_base, 'username')
+if not config.exists(base):
+ exit(0)
+
+for ifname in config.list_nodes(base):
+ tmp_config = base + [ifname, 'default-route']
+ if config.exists(tmp_config):
+ # Retrieve current config value
+ value = config.return_value(tmp_config)
+ # Delete old Config node
+ config.delete(tmp_config)
+ if value == 'none':
+ config.set(base + [ifname, 'no-default-route'])
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/interfaces/27-to-28 b/src/migration-scripts/interfaces/27-to-28
index ad5bfa653..a0d043d11 100755
--- a/src/migration-scripts/interfaces/27-to-28
+++ b/src/migration-scripts/interfaces/27-to-28
@@ -14,8 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-# T5034: tunnel: rename "multicast enable" CLI node to "enable-multicast"
-# valueless node.
+# T4995: pppoe, wwan, sstpc-client rename "authentication user" CLI node
+# to "authentication username"
from sys import argv
@@ -30,21 +30,16 @@ file_name = argv[1]
with open(file_name, 'r') as f:
config_file = f.read()
-base = ['interfaces', 'tunnel']
config = ConfigTree(config_file)
-if not config.exists(base):
- exit(0)
-
-for ifname in config.list_nodes(base):
- multicast_base = base + [ifname, 'multicast']
- if config.exists(multicast_base):
- tmp = config.return_value(multicast_base)
- print(tmp)
- # Delete old Config node
- config.delete(multicast_base)
- if tmp == 'enable':
- config.set(base + [ifname, 'enable-multicast'])
+for type in ['pppoe', 'sstpc-client', 'wwam']:
+ base = ['interfaces', type]
+ if not config.exists(base):
+ continue
+ for interface in config.list_nodes(base):
+ auth_base = base + [interface, 'authentication', 'user']
+ if config.exists(auth_base):
+ config.rename(auth_base, 'username')
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/interfaces/28-to-29 b/src/migration-scripts/interfaces/28-to-29
index acb6ee1fb..ad5bfa653 100755
--- a/src/migration-scripts/interfaces/28-to-29
+++ b/src/migration-scripts/interfaces/28-to-29
@@ -14,7 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-# T5286: remove XDP support in favour of VPP
+# T5034: tunnel: rename "multicast enable" CLI node to "enable-multicast"
+# valueless node.
from sys import argv
@@ -29,17 +30,21 @@ file_name = argv[1]
with open(file_name, 'r') as f:
config_file = f.read()
-supports_xdp = ['bonding', 'ethernet']
+base = ['interfaces', 'tunnel']
config = ConfigTree(config_file)
-for if_type in supports_xdp:
- base = ['interfaces', if_type]
- if not config.exists(base):
- continue
- for interface in config.list_nodes(base):
- if_base = base + [interface]
- if config.exists(if_base + ['xdp']):
- config.delete(if_base + ['xdp'])
+if not config.exists(base):
+ exit(0)
+
+for ifname in config.list_nodes(base):
+ multicast_base = base + [ifname, 'multicast']
+ if config.exists(multicast_base):
+ tmp = config.return_value(multicast_base)
+ print(tmp)
+ # Delete old Config node
+ config.delete(multicast_base)
+ if tmp == 'enable':
+ config.set(base + [ifname, 'enable-multicast'])
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/interfaces/29-to-30 b/src/migration-scripts/interfaces/29-to-30
index 97e1b329c..acb6ee1fb 100755
--- a/src/migration-scripts/interfaces/29-to-30
+++ b/src/migration-scripts/interfaces/29-to-30
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 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
@@ -13,42 +13,37 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# Deletes Wireguard peers if they have the same public key as the router has.
-import sys
+
+# T5286: remove XDP support in favour of VPP
+
+from sys import argv
+
+from vyos.ethtool import Ethtool
from vyos.configtree import ConfigTree
-from vyos.utils.network import is_wireguard_key_pair
-if __name__ == '__main__':
- if len(sys.argv) < 2:
- print("Must specify file name!")
- sys.exit(1)
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
- file_name = sys.argv[1]
+file_name = argv[1]
+with open(file_name, 'r') as f:
+ config_file = f.read()
- with open(file_name, 'r') as f:
- config_file = f.read()
+supports_xdp = ['bonding', 'ethernet']
+config = ConfigTree(config_file)
- config = ConfigTree(config_file)
- base = ['interfaces', 'wireguard']
+for if_type in supports_xdp:
+ base = ['interfaces', if_type]
if not config.exists(base):
- # Nothing to do
- sys.exit(0)
+ continue
for interface in config.list_nodes(base):
- private_key = config.return_value(base + [interface, 'private-key'])
- interface_base = base + [interface]
- if config.exists(interface_base + ['peer']):
- for peer in config.list_nodes(interface_base + ['peer']):
- peer_base = interface_base + ['peer', peer]
- peer_public_key = config.return_value(peer_base + ['public-key'])
- if config.exists(peer_base + ['public-key']):
- if not config.exists(peer_base + ['disable']) \
- and is_wireguard_key_pair(private_key, peer_public_key):
- config.set(peer_base + ['disable'])
-
- try:
- with open(file_name, 'w') as f:
- f.write(config.to_string())
- except OSError as e:
- print("Failed to save the modified config: {}".format(e))
- sys.exit(1)
+ if_base = base + [interface]
+ if config.exists(if_base + ['xdp']):
+ config.delete(if_base + ['xdp'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/nat/6-to-7 b/src/migration-scripts/nat/6-to-7
index b5f6328ef..a2e735394 100755
--- a/src/migration-scripts/nat/6-to-7
+++ b/src/migration-scripts/nat/6-to-7
@@ -21,6 +21,7 @@
# to
# 'set nat [source|destination] rule X [inbound-interface|outbound interface] name <iface>'
# 'set nat [source|destination] rule X [inbound-interface|outbound interface] group <iface_group>'
+# Also remove command if interface == any
from sys import argv,exit
from vyos.configtree import ConfigTree
@@ -56,8 +57,11 @@ for direction in ['source', 'destination']:
if config.exists(base + [iface]):
if config.exists(base + [iface, 'interface-name']):
tmp = config.return_value(base + [iface, 'interface-name'])
- config.delete(base + [iface, 'interface-name'])
- config.set(base + [iface, 'name'], value=tmp)
+ if tmp != 'any':
+ config.delete(base + [iface, 'interface-name'])
+ config.set(base + [iface, 'name'], value=tmp)
+ else:
+ config.delete(base + [iface])
try:
with open(file_name, 'w') as f:
diff --git a/src/op_mode/clear_dhcp_lease.py b/src/op_mode/clear_dhcp_lease.py
index f372d3af0..2c95a2b08 100755
--- a/src/op_mode/clear_dhcp_lease.py
+++ b/src/op_mode/clear_dhcp_lease.py
@@ -1,20 +1,34 @@
#!/usr/bin/env python3
+#
+# Copyright 2023 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 argparse
import re
-from isc_dhcp_leases import Lease
-from isc_dhcp_leases import IscDhcpLeases
-
from vyos.configquery import ConfigTreeQuery
+from vyos.kea import kea_parse_leases
from vyos.utils.io import ask_yes_no
from vyos.utils.process import call
from vyos.utils.commit import commit_in_progress
+# TODO: Update to use Kea control socket command "lease4-del"
config = ConfigTreeQuery()
base = ['service', 'dhcp-server']
-lease_file = '/config/dhcpd.leases'
+lease_file = '/config/dhcp4.leases'
def del_lease_ip(address):
@@ -25,8 +39,7 @@ def del_lease_ip(address):
"""
with open(lease_file, encoding='utf-8') as f:
data = f.read().rstrip()
- lease_config_ip = '{(?P<config>[\s\S]+?)\n}'
- pattern = rf"lease {address} {lease_config_ip}"
+ pattern = rf"^{address},[^\n]+\n"
# Delete lease for ip block
data = re.sub(pattern, '', data)
@@ -38,15 +51,13 @@ def is_ip_in_leases(address):
"""
Return True if address found in the lease file
"""
- leases = IscDhcpLeases(lease_file)
+ leases = kea_parse_leases(lease_file)
lease_ips = []
- for lease in leases.get():
- lease_ips.append(lease.ip)
- if address not in lease_ips:
- print(f'Address "{address}" not found in "{lease_file}"')
- return False
- return True
-
+ for lease in leases:
+ if address == lease['address']:
+ return True
+ print(f'Address "{address}" not found in "{lease_file}"')
+ return False
if not config.exists(base):
print('DHCP-server not configured!')
@@ -75,4 +86,4 @@ if __name__ == '__main__':
exit(1)
else:
del_lease_ip(address)
- call('systemctl restart isc-dhcp-server.service')
+ call('systemctl restart kea-dhcp4-server.service')
diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py
index d6b8aa0b8..a9271ea79 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.py
@@ -21,7 +21,6 @@ import typing
from datetime import datetime
from glob import glob
from ipaddress import ip_address
-from isc_dhcp_leases import IscDhcpLeases
from tabulate import tabulate
import vyos.opmode
@@ -29,6 +28,9 @@ import vyos.opmode
from vyos.base import Warning
from vyos.configquery import ConfigTreeQuery
+from vyos.kea import kea_get_active_config
+from vyos.kea import kea_get_pool_from_subnet_id
+from vyos.kea import kea_parse_leases
from vyos.utils.dict import dict_search
from vyos.utils.file import read_file
from vyos.utils.process import cmd
@@ -77,67 +79,62 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig
Get DHCP server leases
:return list
"""
- lease_file = '/config/dhcpdv6.leases' if family == 'inet6' else '/config/dhcpd.leases'
+ lease_file = '/config/dhcp6.leases' if family == 'inet6' else '/config/dhcp4.leases'
data = []
- leases = IscDhcpLeases(lease_file).get()
+ leases = kea_parse_leases(lease_file)
if pool is None:
pool = _get_dhcp_pools(family=family)
- aux = False
else:
pool = [pool]
- aux = True
-
- ## Search leases for every pool
- for pool_name in pool:
- for lease in leases:
- if lease.sets.get('shared-networkname', '') == pool_name or lease.sets.get('shared-networkname', '') == '':
- #if lease.sets.get('shared-networkname', '') == pool_name:
- data_lease = {}
- data_lease['ip'] = lease.ip
- data_lease['state'] = lease.binding_state
- #data_lease['pool'] = pool_name if lease.sets.get('shared-networkname', '') != '' else 'Fail-Over Server'
- data_lease['pool'] = lease.sets.get('shared-networkname', '')
- data_lease['end'] = lease.end.timestamp() if lease.end else None
- data_lease['origin'] = 'local' if data_lease['pool'] != '' else 'remote'
-
- if family == 'inet':
- data_lease['mac'] = lease.ethernet
- data_lease['start'] = lease.start.timestamp()
- data_lease['hostname'] = lease.hostname
-
- if family == 'inet6':
- data_lease['last_communication'] = lease.last_communication.timestamp()
- data_lease['iaid_duid'] = _format_hex_string(lease.host_identifier_string)
- lease_types_long = {'na': 'non-temporary', 'ta': 'temporary', 'pd': 'prefix delegation'}
- data_lease['type'] = lease_types_long[lease.type]
-
- data_lease['remaining'] = '-'
-
- if lease.end:
- data_lease['remaining'] = lease.end - datetime.utcnow()
-
- if data_lease['remaining'].days >= 0:
- # substraction gives us a timedelta object which can't be formatted with strftime
- # so we use str(), split gets rid of the microseconds
- data_lease['remaining'] = str(data_lease["remaining"]).split('.')[0]
-
- # Do not add old leases
- if data_lease['remaining'] != '' and data_lease['state'] != 'free':
- if not state or data_lease['state'] in state or state == 'all':
- if not origin or data_lease['origin'] in origin:
- if not aux or (aux and data_lease['pool'] == pool_name):
- data.append(data_lease)
-
- # deduplicate
- checked = []
- for entry in data:
- addr = entry.get('ip')
- if addr not in checked:
- checked.append(addr)
- else:
- idx = _find_list_of_dict_index(data, key='ip', value=addr)
- data.pop(idx)
+
+ inet_suffix = '6' if family == 'inet6' else '4'
+ active_config = kea_get_active_config(inet_suffix)
+
+ for lease in leases:
+ data_lease = {}
+ data_lease['ip'] = lease['address']
+ lease_state_long = {'0': 'active', '1': 'rejected', '2': 'expired'}
+ data_lease['state'] = lease_state_long[lease['state']]
+ data_lease['pool'] = kea_get_pool_from_subnet_id(active_config, inet_suffix, lease['subnet_id']) if active_config else '-'
+ data_lease['end'] = lease['expire_timestamp'].timestamp() if lease['expire_timestamp'] else None
+ data_lease['origin'] = 'local' # TODO: Determine remote in HA
+
+ if family == 'inet':
+ data_lease['mac'] = lease['hwaddr']
+ data_lease['start'] = lease['start_timestamp'].timestamp()
+ data_lease['hostname'] = lease['hostname']
+
+ if family == 'inet6':
+ data_lease['last_communication'] = lease['start_timestamp'].timestamp()
+ data_lease['iaid_duid'] = _format_hex_string(lease['duid'])
+ lease_types_long = {'0': 'non-temporary', '1': 'temporary', '2': 'prefix delegation'}
+ data_lease['type'] = lease_types_long[lease['lease_type']]
+
+ data_lease['remaining'] = '-'
+
+ if lease['expire']:
+ data_lease['remaining'] = lease['expire_timestamp'] - datetime.utcnow()
+
+ if data_lease['remaining'].days >= 0:
+ # substraction gives us a timedelta object which can't be formatted with strftime
+ # so we use str(), split gets rid of the microseconds
+ data_lease['remaining'] = str(data_lease["remaining"]).split('.')[0]
+
+ # Do not add old leases
+ if data_lease['remaining'] != '' and data_lease['pool'] in pool and data_lease['state'] != 'free':
+ if not state or state == 'all' or data_lease['state'] in state:
+ data.append(data_lease)
+
+ # deduplicate
+ checked = []
+ for entry in data:
+ addr = entry.get('ip')
+ if addr not in checked:
+ checked.append(addr)
+ else:
+ idx = _find_list_of_dict_index(data, key='ip', value=addr)
+ data.pop(idx)
if sorted:
if sorted == 'ip':
@@ -282,10 +279,9 @@ def show_server_leases(raw: bool, family: ArgFamily, pool: typing.Optional[str],
sorted: typing.Optional[str], state: typing.Optional[ArgState],
origin: typing.Optional[ArgOrigin] ):
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
- v = '6' if family == 'inet6' else ''
- service_name = 'DHCPv6' if family == 'inet6' else 'DHCP'
- if not is_systemd_service_running(f'isc-dhcp-server{v}.service'):
- Warning(f'{service_name} server is configured but not started. Data may be stale.')
+ v = '6' if family == 'inet6' else '4'
+ if not is_systemd_service_running(f'kea-dhcp{v}-server.service'):
+ Warning('DHCP server is configured but not started. Data may be stale.')
v = 'v6' if family == 'inet6' else ''
if pool and pool not in _get_dhcp_pools(family=family):
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index b3e6e518c..6a8797aec 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -22,6 +22,7 @@ from pathlib import Path
from shutil import copy, chown, rmtree, copytree
from glob import glob
from sys import exit
+from os import environ
from time import sleep
from typing import Union
from urllib.parse import urlparse
@@ -83,6 +84,8 @@ DIR_KERNEL_SRC: str = '/boot/'
FILE_ROOTFS_SRC: str = '/usr/lib/live/mount/medium/live/filesystem.squashfs'
ISO_DOWNLOAD_PATH: str = '/tmp/vyos_installation.iso'
+external_download_script = '/usr/libexec/vyos/simple-download.py'
+
# default boot variables
DEFAULT_BOOT_VARS: dict[str, str] = {
'timeout': '5',
@@ -179,6 +182,7 @@ def create_partitions(target_disk: str, target_size: int,
rootfs_size: int = available_size
print(MSG_INFO_INSTALL_PARTITONING)
+ raid.clear()
disk.disk_cleanup(target_disk)
disk_details: disk.DiskDetails = disk.parttable_create(target_disk,
rootfs_size)
@@ -459,8 +463,23 @@ def validate_signature(file_path: str, sign_type: str) -> None:
else:
print('Signature is valid')
-
-def image_fetch(image_path: str, no_prompt: bool = False) -> Path:
+def download_file(local_file: str, remote_path: str, vrf: str,
+ username: str, password: str,
+ progressbar: bool = False, check_space: bool = False):
+ environ['REMOTE_USERNAME'] = username
+ environ['REMOTE_PASSWORD'] = password
+ if vrf is None:
+ download(local_file, remote_path, progressbar=progressbar,
+ check_space=check_space, raise_error=True)
+ else:
+ vrf_cmd = f'REMOTE_USERNAME={username} REMOTE_PASSWORD={password} \
+ ip vrf exec {vrf} {external_download_script} \
+ --local-file {local_file} --remote-path {remote_path}'
+ cmd(vrf_cmd)
+
+def image_fetch(image_path: str, vrf: str = None,
+ username: str = '', password: str = '',
+ no_prompt: bool = False) -> Path:
"""Fetch an ISO image
Args:
@@ -473,14 +492,17 @@ def image_fetch(image_path: str, no_prompt: bool = False) -> Path:
# check a type of path
if urlparse(image_path).scheme:
# download an image
- download(ISO_DOWNLOAD_PATH, image_path, True, True,
- raise_error=True)
+ download_file(ISO_DOWNLOAD_PATH, image_path, vrf,
+ username, password,
+ progressbar=True, check_space=True)
+
# download a signature
sign_file = (False, '')
for sign_type in ['minisig', 'asc']:
try:
- download(f'{ISO_DOWNLOAD_PATH}.{sign_type}',
- f'{image_path}.{sign_type}', raise_error=True)
+ download_file(f'{ISO_DOWNLOAD_PATH}.{sign_type}',
+ f'{image_path}.{sign_type}', vrf,
+ username, password)
sign_file = (True, sign_type)
break
except Exception:
@@ -501,8 +523,8 @@ def image_fetch(image_path: str, no_prompt: bool = False) -> Path:
return local_path
else:
raise FileNotFoundError
- except Exception:
- print(f'The image cannot be fetched from: {image_path}')
+ except Exception as e:
+ print(f'The image cannot be fetched from: {image_path} {e}')
exit(1)
@@ -611,7 +633,8 @@ def install_image() -> None:
print(MSG_WARN_IMAGE_NAME_WRONG)
# ask for password
- user_password: str = ask_input(MSG_INPUT_PASSWORD, default='vyos')
+ user_password: str = ask_input(MSG_INPUT_PASSWORD, default='vyos',
+ no_echo=True)
# ask for default console
console_type: str = ask_input(MSG_INPUT_CONSOLE_TYPE,
@@ -730,7 +753,8 @@ def install_image() -> None:
@compat.grub_cfg_update
-def add_image(image_path: str, no_prompt: bool = False) -> None:
+def add_image(image_path: str, vrf: str = None, username: str = '',
+ password: str = '', no_prompt: bool = False) -> None:
"""Add a new image
Args:
@@ -740,7 +764,7 @@ def add_image(image_path: str, no_prompt: bool = False) -> None:
exit(MSG_ERR_LIVE)
# fetch an image
- iso_path: Path = image_fetch(image_path, no_prompt)
+ iso_path: Path = image_fetch(image_path, vrf, username, password, no_prompt)
try:
# mount an ISO
Path(DIR_ISO_MOUNT).mkdir(mode=0o755, parents=True)
@@ -840,10 +864,15 @@ def parse_arguments() -> Namespace:
choices=['install', 'add'],
required=True,
help='action to perform with an image')
+ parser.add_argument('--vrf',
+ help='vrf name for image download')
parser.add_argument('--no-prompt', action='store_true',
help='perform action non-interactively')
- parser.add_argument(
- '--image-path',
+ parser.add_argument('--username', default='',
+ help='username for image download')
+ parser.add_argument('--password', default='',
+ help='password for image download')
+ parser.add_argument('--image-path',
help='a path (HTTP or local file) to an image that needs to be installed'
)
# parser.add_argument('--image_new_name', help='a new name for image')
@@ -861,7 +890,8 @@ if __name__ == '__main__':
if args.action == 'install':
install_image()
if args.action == 'add':
- add_image(args.image_path, args.no_prompt)
+ add_image(args.image_path, args.vrf,
+ args.username, args.password, args.no_prompt)
exit()
diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py
index 71a40c0e1..2bc7e24fe 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -28,9 +28,6 @@ from vyos.configquery import ConfigTreeQuery
from vyos.utils.process import cmd
from vyos.utils.dict import dict_search
-base = 'nat'
-unconf_message = 'NAT is not configured'
-
ArgDirection = typing.Literal['source', 'destination']
ArgFamily = typing.Literal['inet', 'inet6']
@@ -293,8 +290,9 @@ def _verify(func):
@wraps(func)
def _wrapper(*args, **kwargs):
config = ConfigTreeQuery()
+ base = 'nat66' if 'inet6' in sys.argv[1:] else 'nat'
if not config.exists(base):
- raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ raise vyos.opmode.UnconfiguredSubsystem(f'{base.upper()} is not configured')
return func(*args, **kwargs)
return _wrapper
diff --git a/src/pam-configs/mfa-google-authenticator b/src/pam-configs/mfa-google-authenticator
new file mode 100644
index 000000000..9e49e5ef9
--- /dev/null
+++ b/src/pam-configs/mfa-google-authenticator
@@ -0,0 +1,8 @@
+Name: Google Authenticator PAM module (2FA/MFA)
+Default: no
+Priority: 384
+
+Auth-Type: Primary
+Auth:
+ [default=ignore success=ok auth_err=die] pam_google_authenticator.so nullok forward_pass
+
diff --git a/src/system/on-dhcp-event.sh b/src/system/on-dhcp-event.sh
index 49e53d7e1..7b25bf338 100755
--- a/src/system/on-dhcp-event.sh
+++ b/src/system/on-dhcp-event.sh
@@ -15,20 +15,20 @@ if [ $# -lt 5 ]; then
fi
action=$1
-client_name=$2
-client_ip=$3
-client_mac=$4
-domain=$5
+client_name=$LEASE4_HOSTNAME
+client_ip=$LEASE4_ADDRESS
+client_mac=$LEASE4_HWADDR
+domain=$(echo "$client_name" | cut -d"." -f2-)
hostsd_client="/usr/bin/vyos-hostsd-client"
case "$action" in
- commit) # add mapping for new lease
+ leases4_renew|lease4_recover) # add mapping for new lease
if [ -z "$client_name" ]; then
logger -s -t on-dhcp-event "Client name was empty, using MAC \"$client_mac\" instead"
client_name=$(echo "client-"$client_mac | tr : -)
fi
- if [ "$domain" == "..YYZ!" ]; then
+ if [ -z "$domain" ]; then
client_fqdn_name=$client_name
client_search_expr=$client_name
else
@@ -39,7 +39,7 @@ case "$action" in
exit 0
;;
- release) # delete mapping for released address
+ lease4_release|lease4_expire) # delete mapping for released address)
$hostsd_client --delete-hosts --tag "dhcp-server-$client_ip" --apply
exit 0
;;
diff --git a/src/systemd/isc-dhcp-server6.service b/src/systemd/isc-dhcp-server6.service
deleted file mode 100644
index 1345c5fc5..000000000
--- a/src/systemd/isc-dhcp-server6.service
+++ /dev/null
@@ -1,24 +0,0 @@
-[Unit]
-Description=ISC DHCP IPv6 server
-Documentation=man:dhcpd(8)
-RequiresMountsFor=/run
-ConditionPathExists=/run/dhcp-server/dhcpdv6.conf
-After=vyos-router.service
-
-[Service]
-Type=forking
-WorkingDirectory=/run/dhcp-server
-RuntimeDirectory=dhcp-server
-RuntimeDirectoryPreserve=yes
-Environment=PID_FILE=/run/dhcp-server/dhcpdv6.pid CONFIG_FILE=/run/dhcp-server/dhcpdv6.conf LEASE_FILE=/config/dhcpdv6.leases
-PIDFile=/run/dhcp-server/dhcpdv6.pid
-ExecStartPre=/bin/sh -ec '\
-touch ${LEASE_FILE}; \
-chown nobody:nogroup ${LEASE_FILE}* ; \
-chmod 664 ${LEASE_FILE}* ; \
-/usr/sbin/dhcpd -6 -t -T -q -user dhcpd -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} '
-ExecStart=/usr/sbin/dhcpd -6 -q -user dhcpd -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE}
-Restart=always
-
-[Install]
-WantedBy=multi-user.target
diff --git a/src/validators/bgp-large-community-list b/src/validators/bgp-large-community-list
index 80112dfdc..9ba5b27eb 100755
--- a/src/validators/bgp-large-community-list
+++ b/src/validators/bgp-large-community-list
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-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
@@ -17,9 +17,8 @@
import re
import sys
-from vyos.template import is_ipv4
-
pattern = '(.*):(.*):(.*)'
+allowedChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '+', '*', '?', '^', '$', '(', ')', '[', ']', '{', '}', '|', '\\', ':', '-' }
if __name__ == '__main__':
if len(sys.argv) != 2:
@@ -29,8 +28,7 @@ if __name__ == '__main__':
if not len(value) == 3:
sys.exit(1)
- if not (re.match(pattern, sys.argv[1]) and
- (is_ipv4(value[0]) or value[0].isdigit()) and (value[1].isdigit() or value[1] == '*')):
+ if not (re.match(pattern, sys.argv[1]) and set(sys.argv[1]).issubset(allowedChars)):
sys.exit(1)
sys.exit(0)