pax_global_header 0000666 0000000 0000000 00000000064 14536110317 0014513 g ustar 00root root 0000000 0000000 52 comment=eb74543305493f7902ee58b0e7a5297e2b781d7e
netplan-0.107.1/ 0000775 0000000 0000000 00000000000 14536110317 0013322 5 ustar 00root root 0000000 0000000 netplan-0.107.1/.coveragerc 0000664 0000000 0000000 00000000044 14536110317 0015441 0 ustar 00root root 0000000 0000000 [run]
concurrency = multiprocessing
netplan-0.107.1/.editorconfig 0000664 0000000 0000000 00000000164 14536110317 0016000 0 ustar 00root root 0000000 0000000 root = true
[*.{c,h,py}]
indent_style = space
indent_size = 4
[*.{yml,yaml}]
indent_style = space
indent_size = 2
netplan-0.107.1/.github/ 0000775 0000000 0000000 00000000000 14536110317 0014662 5 ustar 00root root 0000000 0000000 netplan-0.107.1/.github/pull_request_template.md 0000664 0000000 0000000 00000000441 14536110317 0021622 0 ustar 00root root 0000000 0000000
## Description
## Checklist
- [ ] Runs `make check` successfully.
- [ ] Retains 100% code coverage (`make check-coverage`).
- [ ] New/changed keys in YAML format are documented.
- [ ] \(Optional\) Adds example YAML for new feature.
- [ ] \(Optional\) Closes an open bug in Launchpad.
netplan-0.107.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14536110317 0016717 5 ustar 00root root 0000000 0000000 netplan-0.107.1/.github/workflows/autopkgtest.yml 0000664 0000000 0000000 00000004777 14536110317 0022033 0 ustar 00root root 0000000 0000000 name: Autopkgtest CI
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the main branch
on:
push:
branches: [ main ]
pull_request:
branches: [ '**' ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
lxd-ubuntu-jammy:
# The type of runner that the job will run on
runs-on: ubuntu-22.04
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
# Setup LXD + Docker fixes
- uses: canonical/setup-lxd@v0.1.1
with:
channel: latest/stable # switch from distro's LTS channel to latest/stable
- run: |
git fetch --unshallow --tags
# Install openvswitch-switch to make the OVS integration tests work
# Install linux-modules-extra-azure to provide the 'vrf' kernel module,
# it's needed (will be auto-loaded) by routing.test_vrf_basic
- name: Install dependencies
run: |
sudo sed -i '/deb-src/s/^# //' /etc/apt/sources.list
sudo apt update
sudo apt install autopkgtest ubuntu-dev-tools devscripts openvswitch-switch linux-modules-extra-$(uname -r)
# work around LP: #1878225 as fallback
- name: Preparing autopkgtest-build-lxd
run: |
sudo patch /usr/bin/autopkgtest-build-lxd .github/workflows/snapd.patch
autopkgtest-build-lxd ubuntu-daily:jammy
- name: Prepare test
run: |
pull-lp-source netplan.io
cp -r netplan.io-*/debian .
rm -r debian/patches/ # clear any distro patches
echo "usr/lib/python3/dist-packages/netplan/*" >> debian/netplan.io.install # bindings
TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) # find latest (stable) tag
REV=$(git rev-parse --short HEAD) # get current git revision
VER="$TAG+git~$REV"
dch -v "$VER" "Autopkgtest CI testing (Jammy)"
- name: Run autopkgtest (incl. build)
run: |
# using --setup-commands temporarily to install:
# cmocka/pytest/rich/ethtool until they become proper test-deps
autopkgtest . --setup-commands='apt -y install ethtool python3-rich python3-pytest python3-pytest-cov python3-cffi libpython3-dev libcmocka-dev' -U --env=DPKG_GENSYMBOLS_CHECK_LEVEL=0 --env=DEB_BUILD_OPTIONS=nocheck -- lxd autopkgtest/ubuntu/jammy/amd64
netplan-0.107.1/.github/workflows/build-abi.yml 0000664 0000000 0000000 00000003217 14536110317 0021275 0 ustar 00root root 0000000 0000000 name: Build & ABI compatibility
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the main branch
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-22.04
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
# Installs the build dependencies
# Always include phased updates (LP: #1979244)
- name: Install build depends
run: |
echo "APT::Get::Always-Include-Phased-Updates \"true\";" | sudo tee /etc/apt/apt.conf.d/90phased-updates
sudo sed -i '/deb-src/s/^# //' /etc/apt/sources.list
sudo apt update
#sudo apt install lcov python3-coverage curl
sudo apt install abigail-tools meson python3-coverage python3-pytest python3-pytest-cov python3-cffi libpython3-dev
sudo apt build-dep netplan.io
# Runs the build
- name: Run build
run: |
meson setup _build -Dunit_testing=false --prefix=/usr
meson compile -C _build
# Abigail ABI checker
- name: Check ABI compatibility
run: |
abidiff abi-compat/jammy_0.107.xml _build/src/libnetplan.so.0.0 --headers-dir2 include/ --header-file2 src/abi.h --suppressions abi-compat/suppressions.abignore --no-added-syms
netplan-0.107.1/.github/workflows/check-address-sanitizer.yml 0000664 0000000 0000000 00000001722 14536110317 0024152 0 ustar 00root root 0000000 0000000 name: Check for memory issues
# This action will compile netplan with ASAN (address sanitizer) and run
# all the C unit tests and call the generator for every single file in the
# examples directory.
# The job will fail if a memory issue is detected by ASAN.
on:
push:
branches: [ main ]
pull_request:
branches: [ '**' ]
jobs:
memory-sanitizer:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Install build depends
run: |
echo "APT::Get::Always-Include-Phased-Updates \"true\";" | sudo tee /etc/apt/apt.conf.d/90phased-updates
sudo sed -i '/deb-src/s/^# //' /etc/apt/sources.list
sudo apt update
sudo apt -y install python3-rich python3-coverage python3-pytest python3-pytest-cov curl meson gcovr expect libcmocka-dev python3-cffi libpython3-dev
sudo apt -y build-dep netplan.io
- name: Run unit tests
run: |
unbuffer ./tools/run_asan.sh
netplan-0.107.1/.github/workflows/check-coverage.yml 0000664 0000000 0000000 00000004021 14536110317 0022305 0 ustar 00root root 0000000 0000000 name: Unit tests & Coverage
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the main branch
on:
push:
branches: [ main ]
pull_request:
branches: [ '**' ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "test-and-coverage"
test-and-coverage:
# The type of runner that the job will run on
runs-on: ubuntu-22.04
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
# Installs the build dependencies, simulating a TTY/PTY via 'unbuffer' to
# to fix test_terminal.py on GA (https://github.com/actions/runner/issues/241)
# Always include phased updates (LP: #1979244)
- name: Install build depends
run: |
echo "APT::Get::Always-Include-Phased-Updates \"true\";" | sudo tee /etc/apt/apt.conf.d/90phased-updates
sudo sed -i '/deb-src/s/^# //' /etc/apt/sources.list
sudo apt update
sudo apt install python3-rich python3-coverage python3-pytest python3-pytest-cov curl meson gcovr expect libcmocka-dev python3-cffi libpython3-dev
sudo apt build-dep netplan.io
wget http://archive.ubuntu.com/ubuntu/pool/universe/g/gcovr/gcovr_5.2-1_all.deb
sudo dpkg -i gcovr*.deb # we need newer gcovr to make the gcovr.cfg:exclude setting work
# Runs the unit tests with coverage
- name: Run unit tests
run: |
meson setup _build-cov --prefix=/usr -Db_coverage=true -Dunit_testing=true
meson compile -C _build-cov
unbuffer meson test -C _build-cov --verbose
# Checks the coverage diff to the main branch
#- name: Upload coverage to Codecov
# uses: codecov/codecov-action@v1
# with:
# name: check-coverage
# fail_ci_if_error: true
# verbose: true
netplan-0.107.1/.github/workflows/check-wording.yaml 0000664 0000000 0000000 00000000530 14536110317 0022325 0 ustar 00root root 0000000 0000000 name: Check for non-inclusive language
on:
push:
branches: [ main ]
pull_request:
branches: [ '**' ]
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Run Woke action
uses: get-woke/woke-action@v0
with:
fail-on-error: true
woke-args: -o text
netplan-0.107.1/.github/workflows/codeql-analysis.yml 0000664 0000000 0000000 00000005210 14536110317 0022530 0 ustar 00root root 0000000 0000000 # For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '17 21 * * 2'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
language: [ 'cpp', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Installs the build dependencies
- name: Install build depends
run: |
sudo sed -i '/deb-src/s/^# //' /etc/apt/sources.list
sudo apt update
sudo apt install meson python3-coverage python3-pytest python3-pytest-cov libcmocka-dev python3-cffi libpython3-dev
sudo apt build-dep netplan.io
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
netplan-0.107.1/.github/workflows/coverity.yml 0000664 0000000 0000000 00000003006 14536110317 0021305 0 ustar 00root root 0000000 0000000 name: Coverity
on:
schedule:
- cron: '0 0 * * MON'
jobs:
coverity:
if: github.repository == 'canonical/netplan'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
sudo sed -i '/deb-src/s/^# //' /etc/apt/sources.list
sudo apt update
sudo apt -y build-dep netplan.io
sudo apt -y install libcmocka-dev meson python3-pytest curl python3-cffi libpython3-dev
- name: Download Coverity
run: |
curl https://scan.coverity.com/download/cxx/linux64 --no-progress-meter --output ${HOME}/coverity.tar.gz --data "token=${{ secrets.COVERITY_TOKEN }}&project=Netplan"
mkdir ${HOME}/coverity
tar --strip=1 -C ${HOME}/coverity -xzf ${HOME}/coverity.tar.gz
echo "$HOME/coverity/bin" >> $GITHUB_PATH
- name: Run Coverity
run: |
meson setup coveritybuild --prefix=/usr
cov-build --dir cov-int meson compile -C coveritybuild
tar czf netplan.tar.gz cov-int
- name: Upload results
run: |
TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) # find latest (stable) tag
REV=$(git rev-parse --short HEAD) # get current git revision
VER="$TAG+git~$REV"
curl --form token=${{ secrets.COVERITY_TOKEN }} --form email=${{ secrets.COVERITY_EMAIL }} --form file=@netplan.tar.gz --form version="${VER}" --form description="Coverity scan" https://scan.coverity.com/builds?project=Netplan
netplan-0.107.1/.github/workflows/debci.yml 0000664 0000000 0000000 00000006347 14536110317 0020522 0 ustar 00root root 0000000 0000000 name: Autopkgtest DebCI
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the main branch
on:
push:
branches: [ main, 'stable/**' ]
pull_request:
branches: [ '**' ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
lxc-debian-testing:
# The type of runner that the job will run on
runs-on: ubuntu-22.04
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- run: |
git fetch --unshallow --tags
# Install openvswitch-switch to make the OVS integration tests work
# Install linux-modules-extra-azure to provide the 'vrf' kernel module,
# it's needed (will be auto-loaded) by routing.test_vrf_basic
- name: Install dependencies
run: |
sudo sed -i '/deb-src/s/^# //' /etc/apt/sources.list
sudo apt update
sudo apt install debci lxc lxc-templates debian-archive-keyring autopkgtest ubuntu-dev-tools devscripts linux-modules-extra-$(uname -r) #openvswitch-switch
# See: https://discourse.ubuntu.com/t/containers-lxc/11526 (Apparmor section)
# (LP: #1950787, LP: #1998943)
- name: Preparing autopkgtest-build-lxc
run: |
# Fix Docker blocking LXC networking:
# https://discuss.linuxcontainers.org/t/9953/4
sudo iptables -I DOCKER-USER -j ACCEPT
sudo apparmor_parser -R /etc/apparmor.d/usr.bin.lxc-start
sudo ln -s /etc/apparmor.d/usr.bin.lxc-start /etc/apparmor.d/disable/
echo "lxc.apparmor.profile = unconfined" | sudo tee -a /etc/lxc/default.conf
sudo debci setup -s testing -a amd64 -b lxc
- name: Prepare test
run: |
# pull-debian-source netplan.io # snapshot.debian.org is not up-to-date
V=$(rmadison -u debian -s unstable netplan.io | cut -d"|" -f2 | xargs)
dget -u "https://deb.debian.org/debian/pool/main/n/netplan.io/netplan.io_$V.dsc"
cp -r netplan.io-*/debian .
rm -r debian/patches/ # clear any distro patches
echo "usr/lib/python3/dist-packages/netplan/*" >> debian/netplan.io.install # bindings
echo "override_dh_auto_configure:" >> debian/rules
echo " dh_auto_configure -- -Dpython.purelibdir=/usr/lib/python3/dist-packages -Dpython.platlibdir=/usr/lib/python3/dist-packages" >> debian/rules
TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) # find latest (stable) tag
REV=$(git rev-parse --short HEAD) # get current git revision
VER="$TAG+git~$REV"
dch -v "$VER" "Autopkgtest CI testing (Debian testing)"
- name: Run autopkgtest (incl. build)
run: |
# using --setup-commands='apt -y install ...' temporarily to install
# (test-/build-) deps until they become part of the packaging
sudo autopkgtest . -U --env=DPKG_GENSYMBOLS_CHECK_LEVEL=0 --env=DEB_BUILD_OPTIONS=nocheck --setup-commands='apt -y install python3-cffi libpython3-dev' -- lxc autopkgtest-testing-amd64 || test $? -eq 2 # allow OVS test to be skipped (exit code = 2)
netplan-0.107.1/.github/workflows/network-manager.yml 0000664 0000000 0000000 00000006045 14536110317 0022550 0 ustar 00root root 0000000 0000000 name: NetworkManager Autopkgtest
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the main branch
on:
push:
branches: [ main, 'stable/**' ]
pull_request:
branches: [ '**' ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
lxd-network-manager:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
# Setup LXD + Docker fixes
- uses: canonical/setup-lxd@v0.1.1
with:
channel: latest/stable # switch from distro's LTS channel to latest/stable
- run: |
git fetch --unshallow --tags
# Install openvswitch-switch to make the OVS integration tests work
# Install linux-modules-extra-azure to provide the 'vrf' kernel module,
# it's needed (will be auto-loaded) by routing.test_vrf_basic
- name: Install dependencies
run: |
sudo sed -i '/deb-src/s/^# //' /etc/apt/sources.list
sudo apt update
sudo apt install autopkgtest ubuntu-dev-tools devscripts openvswitch-switch linux-modules-extra-$(uname -r)
- name: Prepare test
run: |
pull-lp-source netplan.io
cp -r netplan.io-*/debian .
rm -r debian/patches/ # clear any distro patches
echo "3.0 (native)" > debian/source/format # force native build
echo "usr/lib/python3/dist-packages/netplan/*" >> debian/netplan.io.install # bindings
echo "override_dh_auto_configure:" >> debian/rules
echo " dh_auto_configure -- -Dpython.purelibdir=/usr/lib/python3/dist-packages -Dpython.platlibdir=/usr/lib/python3/dist-packages" >> debian/rules
TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) # find latest (stable) tag
REV=$(git rev-parse --short HEAD) # get current git revision
VER="$TAG+git~$REV"
dch -v "$VER" "Autopkgtest CI"
# Build deb
- uses: jtdor/build-deb-action@v1
env:
DEB_BUILD_OPTIONS: nocheck
DPKG_GENSYMBOLS_CHECK_LEVEL: 0
with:
docker-image: ubuntu:mantic
buildpackage-opts: --build=binary --no-sign
extra-build-deps: python3-cffi libpython3-dev
# work around LP: #1878225 as fallback
- name: Preparing autopkgtest-build-lxd
run: |
sudo patch /usr/bin/autopkgtest-build-lxd .github/workflows/snapd.patch
autopkgtest-build-lxd ubuntu-daily:mantic
- name: Run autopkgtest
run: |
# using --setup-commands temporarily to install:
# cmocka/pytest/rich/ethtool until they become proper test-deps
sudo autopkgtest -U debian/artifacts/*.deb network-manager --apt-pocket=proposed=src:network-manager -- lxd autopkgtest/ubuntu/mantic/amd64 || test $? -eq 2 # allow for skipped tests (exit code = 2)
netplan-0.107.1/.github/workflows/rpmbuild.yml 0000664 0000000 0000000 00000001615 14536110317 0021263 0 ustar 00root root 0000000 0000000 name: RPM build
on:
push:
branches: [ main, 'stable/**' ]
pull_request:
branches: [ '**' ]
jobs:
rpm:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
container:
- fedora:latest
# - fedora:rawhide
- rockylinux:9
container:
image: ${{ matrix.container }}
steps:
- uses: actions/checkout@v2
- name: Build & Test
run: |
cat /etc/os-release
dnf -y install dnf-plugins-core rpmdevtools # for 'dnf builddep'
dnf -y install epel-release || true
dnf config-manager --set-enabled crb || true # Meson/CMocka on EL9
dnf -y install centos-release-nfv-openvswitch || true # OVS on EL9
dnf -y builddep rpm/netplan.spec
adduser test
chown -R test:test .
su test -c 'rpmbuild -bi --build-in-place rpm/netplan.spec'
netplan-0.107.1/.github/workflows/snapd.patch 0000664 0000000 0000000 00000000447 14536110317 0021052 0 ustar 00root root 0000000 0000000 @@ -70,6 +70,8 @@
sleep 5
if lxc exec "$CONTAINER" -- systemctl mask serial-getty@getty.service; then
+ lxc exec "$CONTAINER" -- systemctl mask snapd.service
+ lxc exec "$CONTAINER" -- systemctl mask snapd.seeded.service
lxc exec "$CONTAINER" -- reboot
fi
netplan-0.107.1/.github/workflows/spread.yml 0000664 0000000 0000000 00000000653 14536110317 0020724 0 ustar 00root root 0000000 0000000 name: Run spread
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
spread:
runs-on: ubuntu-latest
steps:
- uses: canonical/setup-lxd@v0.1.1
- uses: actions/checkout@v2
- name: Install spread
run: |
go install github.com/snapcore/spread/cmd/spread@latest
- name: Run the spread test inside LXD
run: |
~/go/bin/spread -v lxd:
netplan-0.107.1/.gitignore 0000664 0000000 0000000 00000000250 14536110317 0015307 0 ustar 00root root 0000000 0000000 generate
netplan-dbus
test-coverage
doc/*.html
doc/*.[1-9]
__pycache__
*.pyc
.coverage
.vscode
src/_features.h
netplan_cli/_features.py
dbus/io.netplan.Netplan.service
netplan-0.107.1/.readthedocs.yaml 0000664 0000000 0000000 00000000751 14536110317 0016554 0 ustar 00root root 0000000 0000000 # .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and environment
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build documentation in the doc/ directory with Sphinx
sphinx:
configuration: doc/conf.py
builder: dirhtml
# Declare the Python requirements required to build your docs
python:
install:
- requirements: doc/requirements.txt
netplan-0.107.1/.woke.yaml 0000664 0000000 0000000 00000006026 14536110317 0015235 0 ustar 00root root 0000000 0000000 ignore_files:
- abi-compat/*.xml
rules:
# Including some terms from https://inclusivenaming.org/ that are not in the default ruleset
- name: abort
terms:
- abort
alternatives:
- force quit
- halt
- close
- name: cripple
terms:
- cripple
alternatives:
- degraded
- restrict
- name: segregate
terms:
- segregate
- segregation
alternatives:
- segment/segmentation
- separate/separation
# From https://github.com/canonical/Inclusive-naming
# Overtly racist terms
- name: whitelist
terms:
- whitelist
- white-list
- whitelisted
- white-listed
- whitelisting
- white-listing
alternatives:
- allowlist
severity: warning
- name: blacklist
terms:
- blacklist
- black-list
- blacklisted
- black-listed
- blacklisting
- black-listing
alternatives:
- denylist
- blocklist
severity: warning
- name: blackhat
terms:
- blackhat
- black hat
alternatives:
- malicious actor
- attacker
severity: warning
- name: illegal characters
terms:
- illegal characters
alternatives:
- invalid characters
- unsupported characters
severity: warning
- name: master
terms:
- master
alternatives:
- primary
- main
severity: warning
- name: slave
terms:
- slave
alternatives:
- secondary
- replica
severity: warning
- name: whitehat
terms:
- whitehat
- white hat
alternatives:
- researcher
- security specialist
severity: warning
# Overtly Sexist/Transphobic/Homophobic/Default Male Gendered Terms/Pejorative about Gender Identity/Discriminative
- name: chairman
terms:
- chairman
- foreman
alternatives:
- chair
- foreperson
severity: warning
- name: grandfathered
terms:
- grandfathered
alternatives:
- legacied
severity: warning
- name: guys
terms:
- guys
alternatives:
- people
- folks
severity: warning
- name: hang
terms:
- hang
alternatives:
- stop responding
- stall
severity: warning
options:
word_boundary: true
- name: man hours
terms:
- man hours
alternatives:
- staff hours
- hours of effort
severity: warning
- name: man in the middle
terms:
- man in the middle
- man-in-the-middle
alternatives:
- machine-in-the-middle
- person-in-the-middle
severity: warning
- name: manned
terms:
- manned
alternatives:
- staffed
- monitored
severity: warning
- name: middleman
terms:
- middleman
alternatives:
- middleperson
- intermediary
severity: warning
- name: sanity check
terms:
- sanity
alternatives:
- confidence check
- coherence check
severity: warning
# Ignore rules
- name: he
- name: dummy
netplan-0.107.1/CONTRIBUTING 0000664 0000000 0000000 00000004750 14536110317 0015162 0 ustar 00root root 0000000 0000000 # Contributing to netplan.io
Thanks for taking the time to contribute to netplan!
Here are the guidelines for contributing to the development of netplan. These
are guidelines, not hard and fast rules; but please exercise judgement. Feel
free to propose changes to this document.
#### Table Of Contents
[Code of Conduct](#code-of-conduct)
[What should I know before I get started](#what-should-i-know-before-i-get-started)
* [Did you find a bug?](#did-you-find-a-bug)
* [Code Quality](#code-quality)
* [Conventions](#conventions)
## Code of Conduct
This project and everyone participating in it is governed by the
[Ubuntu Code of Conduct](https://www.ubuntu.com/community/code-of-conduct).
By participating, you are expected to uphold this code. Please report
unacceptable behavior to
[netplan-developers@lists.launchpad.net](mailto:netplan-developers@lists.launchpad.net).
## What should I know before I get started?
### Did you find a bug?
If you've found a bug, please make sure to report it on Launchpad against the
[netplan](http://bugs.launchpad.net/netplan) project. We do use these bug reports
to make it easier to backport features to supported releases of Ubuntu: having
an existing bug report, with people who understand well how the bug appears
helps immensely in making sure the feature or bug fix is well tested when it is
being released to supported Ubuntu releases.
### Code quality
We want to maintain the quality of the code in netplan to the highest possible
degree. As such, we do insist on keeping a code coverage with unit tests to 100%
coverage if it is possible. If not, please make sure to explain why when submitting
a pull request, and expect reviewers to challenge you on that decision and suggest
a course of action.
### Conventions
The netplan project mixes C and python code. Generator code is generally all
written in C, while the UI / command-line interface is written in python. Please
look at the surrounding code, and make a best effort to follow the general style
used in the code. We do insist on proper indentation (4 spaces), but we will
not block good features and bug fixes on purely style issues. Please exercise
your best judgement: if it looks odd or too clever to you, chances are it will
look odd or too clever to code reviewers. In that case, you may be asked for
some styles changes in a pull request. Similarly, if you see code that you
find hard to understand, we do encourage that you submit pull requests that
help make the code easier to understand and maintain.
netplan-0.107.1/COPYING 0000664 0000000 0000000 00000104513 14536110317 0014361 0 ustar 00root root 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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 .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
netplan-0.107.1/Doxyfile 0000664 0000000 0000000 00000350516 14536110317 0015042 0 ustar 00root root 0000000 0000000 # Doxyfile 1.9.4
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
#
# All text after a double hash (##) is considered a comment and is placed in
# front of the TAG it is preceding.
#
# All text after a single hash (#) is considered a comment and will be ignored.
# The format is:
# TAG = value [value, ...]
# For lists, items can also be appended using:
# TAG += value [value, ...]
# Values that contain spaces should be placed between quotes (\" \").
#
# Note:
#
# Use doxygen to compare the used configuration file with the template
# configuration file:
# doxygen -x [configFile]
# Use doxygen to compare the used configuration file with the template
# configuration file without replacing the environment variables:
# doxygen -x_noenv [configFile]
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
# This tag specifies the encoding used for all characters in the configuration
# file that follow. The default is UTF-8 which is also the encoding used for all
# text before the first occurrence of this tag. Doxygen uses libiconv (or the
# iconv built into libc) for the transcoding. See
# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
# The default value is: UTF-8.
DOXYFILE_ENCODING = UTF-8
# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
# double-quotes, unless you are using Doxywizard) that should identify the
# project for which the documentation is generated. This name is used in the
# title of most generated pages and in a few other places.
# The default value is: My Project.
PROJECT_NAME = "Netplan"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER =
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF =
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
# the logo to the output directory.
PROJECT_LOGO =
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
# into which the generated documentation will be written. If a relative path is
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
OUTPUT_DIRECTORY =
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
# sub-directories (in 2 levels) under the output directory of each output format
# and will distribute the generated files over these directories. Enabling this
# option can be useful when feeding doxygen a huge amount of source files, where
# putting all generated files in the same directory would otherwise causes
# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
# control the number of sub-directories.
# The default value is: NO.
CREATE_SUBDIRS = NO
# Controls the number of sub-directories that will be created when
# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
# level increment doubles the number of directories, resulting in 4096
# directories at level 8 which is the default and also the maximum value. The
# sub-directories are organized in 2 levels, the first level always has a fixed
# numer of 16 directories.
# Minimum value: 0, maximum value: 8, default value: 8.
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
CREATE_SUBDIRS_LEVEL = 8
# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
# characters to appear in the names of generated files. If set to NO, non-ASCII
# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
# U+3044.
# The default value is: NO.
ALLOW_UNICODE_NAMES = NO
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all constant output in the proper language.
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
# English messages), Korean, Korean-en (Korean with English messages), Latvian,
# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
# Swedish, Turkish, Ukrainian and Vietnamese.
# The default value is: English.
OUTPUT_LANGUAGE = English
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
# descriptions after the members that are listed in the file and class
# documentation (similar to Javadoc). Set to NO to disable this.
# The default value is: YES.
BRIEF_MEMBER_DESC = YES
# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
# description of a member or function before the detailed description
#
# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
# brief descriptions will be completely suppressed.
# The default value is: YES.
REPEAT_BRIEF = YES
# This tag implements a quasi-intelligent brief description abbreviator that is
# used to form the text in various listings. Each string in this list, if found
# as the leading text of the brief description, will be stripped from the text
# and the result, after processing the whole list, is used as the annotated
# text. Otherwise, the brief description is used as-is. If left blank, the
# following values are used ($name is automatically replaced with the name of
# the entity):The $name class, The $name widget, The $name file, is, provides,
# specifies, contains, represents, a, an and the.
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
is \
provides \
specifies \
contains \
represents \
a \
an \
the
# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
# doxygen will generate a detailed section even if there is only a brief
# description.
# The default value is: NO.
ALWAYS_DETAILED_SEC = NO
# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
# inherited members of a class in the documentation of that class as if those
# members were ordinary class members. Constructors, destructors and assignment
# operators of the base classes will not be shown.
# The default value is: NO.
INLINE_INHERITED_MEMB = NO
# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
# before files name in the file list and in the header files. If set to NO the
# shortest path that makes the file name unique will be used
# The default value is: YES.
FULL_PATH_NAMES = YES
# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
# Stripping is only done if one of the specified strings matches the left-hand
# part of the path. The tag can be used to show relative paths in the file list.
# If left blank the directory from which doxygen is run is used as the path to
# strip.
#
# Note that you can specify absolute paths here, but also relative paths, which
# will be relative from the directory where doxygen is started.
# This tag requires that the tag FULL_PATH_NAMES is set to YES.
STRIP_FROM_PATH =
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
# path mentioned in the documentation of a class, which tells the reader which
# header file to include in order to use a class. If left blank only the name of
# the header file containing the class definition is used. Otherwise one should
# specify the list of include paths that are normally passed to the compiler
# using the -I flag.
STRIP_FROM_INC_PATH =
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
# less readable) file names. This can be useful is your file systems doesn't
# support long names like on DOS, Mac, or CD-ROM.
# The default value is: NO.
SHORT_NAMES = NO
# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
# first line (until the first dot) of a Javadoc-style comment as the brief
# description. If set to NO, the Javadoc-style will behave just like regular Qt-
# style comments (thus requiring an explicit @brief command for a brief
# description.)
# The default value is: NO.
JAVADOC_AUTOBRIEF = NO
# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
# such as
# /***************
# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
# Javadoc-style will behave just like regular comments and it will not be
# interpreted by doxygen.
# The default value is: NO.
JAVADOC_BANNER = NO
# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
# line (until the first dot) of a Qt-style comment as the brief description. If
# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
# requiring an explicit \brief command for a brief description.)
# The default value is: NO.
QT_AUTOBRIEF = NO
# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
# a brief description. This used to be the default behavior. The new default is
# to treat a multi-line C++ comment block as a detailed description. Set this
# tag to YES if you prefer the old behavior instead.
#
# Note that setting this tag to YES also means that rational rose comments are
# not recognized any more.
# The default value is: NO.
MULTILINE_CPP_IS_BRIEF = NO
# By default Python docstrings are displayed as preformatted text and doxygen's
# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
# doxygen's special commands can be used and the contents of the docstring
# documentation blocks is shown as doxygen documentation.
# The default value is: YES.
PYTHON_DOCSTRING = YES
# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
# documentation from any documented member that it re-implements.
# The default value is: YES.
INHERIT_DOCS = YES
# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
# page for each member. If set to NO, the documentation of a member will be part
# of the file/class/namespace that contains it.
# The default value is: NO.
SEPARATE_MEMBER_PAGES = NO
# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
# uses this value to replace tabs by spaces in code fragments.
# Minimum value: 1, maximum value: 16, default value: 4.
TAB_SIZE = 4
# This tag can be used to specify a number of aliases that act as commands in
# the documentation. An alias has the form:
# name=value
# For example adding
# "sideeffect=@par Side Effects:^^"
# will allow you to put the command \sideeffect (or @sideeffect) in the
# documentation, which will result in a user-defined paragraph with heading
# "Side Effects:". Note that you cannot put \n's in the value part of an alias
# to insert newlines (in the resulting output). You can put ^^ in the value part
# of an alias to insert a newline as if a physical newline was in the original
# file. When you need a literal { or } or , in the value part of an alias you
# have to escape them by means of a backslash (\), this can lead to conflicts
# with the commands \{ and \} for these it is advised to use the version @{ and
# @} or use a double escape (\\{ and \\})
ALIASES =
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
# members will be omitted, etc.
# The default value is: NO.
OPTIMIZE_OUTPUT_FOR_C = NO
# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
# Python sources only. Doxygen will then generate output that is more tailored
# for that language. For instance, namespaces will be presented as packages,
# qualified scopes will look different, etc.
# The default value is: NO.
OPTIMIZE_OUTPUT_JAVA = NO
# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
# sources. Doxygen will then generate output that is tailored for Fortran.
# The default value is: NO.
OPTIMIZE_FOR_FORTRAN = NO
# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
# sources. Doxygen will then generate output that is tailored for VHDL.
# The default value is: NO.
OPTIMIZE_OUTPUT_VHDL = NO
# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
# sources only. Doxygen will then generate output that is more tailored for that
# language. For instance, namespaces will be presented as modules, types will be
# separated into more groups, etc.
# The default value is: NO.
OPTIMIZE_OUTPUT_SLICE = NO
# Doxygen selects the parser to use depending on the extension of the files it
# parses. With this tag you can assign which parser to use for a given
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# tries to guess whether the code is fixed or free formatted code, this is the
# default for Fortran type files). For instance to make doxygen treat .inc files
# as Fortran files (default is PHP), and .f files as C (default is Fortran),
# use: inc=Fortran f=C.
#
# Note: For files without extension you can use no_extension as a placeholder.
#
# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
# the files are not read by doxygen. When specifying no_extension you should add
# * to the FILE_PATTERNS.
#
# Note see also the list of default file extension mappings.
EXTENSION_MAPPING =
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable
# documentation. See https://daringfireball.net/projects/markdown/ for details.
# The output of markdown processing is further processed by doxygen, so you can
# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
# case of backward compatibilities issues.
# The default value is: YES.
MARKDOWN_SUPPORT = YES
# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
# to that level are automatically included in the table of contents, even if
# they do not have an id attribute.
# Note: This feature currently applies only to Markdown headings.
# Minimum value: 0, maximum value: 99, default value: 5.
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
TOC_INCLUDE_HEADINGS = 5
# When enabled doxygen tries to link words that correspond to documented
# classes, or namespaces to their corresponding documentation. Such a link can
# be prevented in individual cases by putting a % sign in front of the word or
# globally by setting AUTOLINK_SUPPORT to NO.
# The default value is: YES.
AUTOLINK_SUPPORT = YES
# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
# to include (a tag file for) the STL sources as input, then you should set this
# tag to YES in order to let doxygen match functions declarations and
# definitions whose arguments contain STL classes (e.g. func(std::string);
# versus func(std::string) {}). This also make the inheritance and collaboration
# diagrams that involve STL classes more complete and accurate.
# The default value is: NO.
BUILTIN_STL_SUPPORT = NO
# If you use Microsoft's C++/CLI language, you should set this option to YES to
# enable parsing support.
# The default value is: NO.
CPP_CLI_SUPPORT = NO
# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
# will parse them like normal C++ but will assume all classes use public instead
# of private inheritance when no explicit protection keyword is present.
# The default value is: NO.
SIP_SUPPORT = NO
# For Microsoft's IDL there are propget and propput attributes to indicate
# getter and setter methods for a property. Setting this option to YES will make
# doxygen to replace the get and set methods by a property in the documentation.
# This will only work if the methods are indeed getting or setting a simple
# type. If this is not the case, or you want to show the methods anyway, you
# should set this option to NO.
# The default value is: YES.
IDL_PROPERTY_SUPPORT = YES
# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
# tag is set to YES then doxygen will reuse the documentation of the first
# member in the group (if any) for the other members of the group. By default
# all members of a group must be documented explicitly.
# The default value is: NO.
DISTRIBUTE_GROUP_DOC = NO
# If one adds a struct or class to a group and this option is enabled, then also
# any nested class or struct is added to the same group. By default this option
# is disabled and one has to add nested compounds explicitly via \ingroup.
# The default value is: NO.
GROUP_NESTED_COMPOUNDS = NO
# Set the SUBGROUPING tag to YES to allow class member groups of the same type
# (for instance a group of public functions) to be put as a subgroup of that
# type (e.g. under the Public Functions section). Set it to NO to prevent
# subgrouping. Alternatively, this can be done per class using the
# \nosubgrouping command.
# The default value is: YES.
SUBGROUPING = YES
# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
# are shown inside the group in which they are included (e.g. using \ingroup)
# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
# and RTF).
#
# Note that this feature does not work in combination with
# SEPARATE_MEMBER_PAGES.
# The default value is: NO.
INLINE_GROUPED_CLASSES = NO
# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
# with only public data fields or simple typedef fields will be shown inline in
# the documentation of the scope in which they are defined (i.e. file,
# namespace, or group documentation), provided this scope is documented. If set
# to NO, structs, classes, and unions are shown on a separate page (for HTML and
# Man pages) or section (for LaTeX and RTF).
# The default value is: NO.
INLINE_SIMPLE_STRUCTS = NO
# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
# enum is documented as struct, union, or enum with the name of the typedef. So
# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
# with name TypeT. When disabled the typedef will appear as a member of a file,
# namespace, or class. And the struct will be named TypeS. This can typically be
# useful for C code in case the coding convention dictates that all compound
# types are typedef'ed and only the typedef is referenced, never the tag name.
# The default value is: NO.
TYPEDEF_HIDES_STRUCT = NO
# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
# cache is used to resolve symbols given their name and scope. Since this can be
# an expensive process and often the same symbol appears multiple times in the
# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
# doxygen will become slower. If the cache is too large, memory is wasted. The
# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
# symbols. At the end of a run doxygen will report the cache usage and suggest
# the optimal cache size from a speed point of view.
# Minimum value: 0, maximum value: 9, default value: 0.
LOOKUP_CACHE_SIZE = 0
# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
# during processing. When set to 0 doxygen will based this on the number of
# cores available in the system. You can set it explicitly to a value larger
# than 0 to get more control over the balance between CPU load and processing
# speed. At this moment only the input processing can be done using multiple
# threads. Since this is still an experimental feature the default is set to 1,
# which effectively disables parallel processing. Please report any issues you
# encounter. Generating dot graphs in parallel is controlled by the
# DOT_NUM_THREADS setting.
# Minimum value: 0, maximum value: 32, default value: 1.
NUM_PROC_THREADS = 1
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
# documentation are documented, even if no documentation was available. Private
# class members and static file members will be hidden unless the
# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
# Note: This will also disable the warnings about undocumented members that are
# normally produced when WARNINGS is set to YES.
# The default value is: NO.
EXTRACT_ALL = NO
# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
# be included in the documentation.
# The default value is: NO.
EXTRACT_PRIVATE = NO
# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
# methods of a class will be included in the documentation.
# The default value is: NO.
EXTRACT_PRIV_VIRTUAL = NO
# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
# scope will be included in the documentation.
# The default value is: NO.
EXTRACT_PACKAGE = NO
# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
# included in the documentation.
# The default value is: NO.
EXTRACT_STATIC = NO
# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
# locally in source files will be included in the documentation. If set to NO,
# only classes defined in header files are included. Does not have any effect
# for Java sources.
# The default value is: YES.
EXTRACT_LOCAL_CLASSES = YES
# This flag is only useful for Objective-C code. If set to YES, local methods,
# which are defined in the implementation section but not in the interface are
# included in the documentation. If set to NO, only methods in the interface are
# included.
# The default value is: NO.
EXTRACT_LOCAL_METHODS = NO
# If this flag is set to YES, the members of anonymous namespaces will be
# extracted and appear in the documentation as a namespace called
# 'anonymous_namespace{file}', where file will be replaced with the base name of
# the file that contains the anonymous namespace. By default anonymous namespace
# are hidden.
# The default value is: NO.
EXTRACT_ANON_NSPACES = NO
# If this flag is set to YES, the name of an unnamed parameter in a declaration
# will be determined by the corresponding definition. By default unnamed
# parameters remain unnamed in the output.
# The default value is: YES.
RESOLVE_UNNAMED_PARAMS = YES
# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
# undocumented members inside documented classes or files. If set to NO these
# members will be included in the various overviews, but no documentation
# section is generated. This option has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.
HIDE_UNDOC_MEMBERS = NO
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy. If set
# to NO, these classes will be included in the various overviews. This option
# has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.
HIDE_UNDOC_CLASSES = NO
# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
# declarations. If set to NO, these declarations will be included in the
# documentation.
# The default value is: NO.
HIDE_FRIEND_COMPOUNDS = NO
# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
# documentation blocks found inside the body of a function. If set to NO, these
# blocks will be appended to the function's detailed documentation block.
# The default value is: NO.
HIDE_IN_BODY_DOCS = NO
# The INTERNAL_DOCS tag determines if documentation that is typed after a
# \internal command is included. If the tag is set to NO then the documentation
# will be excluded. Set it to YES to include the internal documentation.
# The default value is: NO.
INTERNAL_DOCS = NO
# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
# able to match the capabilities of the underlying filesystem. In case the
# filesystem is case sensitive (i.e. it supports files in the same directory
# whose names only differ in casing), the option must be set to YES to properly
# deal with such files in case they appear in the input. For filesystems that
# are not case sensitive the option should be set to NO to properly deal with
# output files written for symbols that only differ in casing, such as for two
# classes, one named CLASS and the other named Class, and to also support
# references to files without having to specify the exact matching casing. On
# Windows (including Cygwin) and MacOS, users should typically set this option
# to NO, whereas on Linux or other Unix flavors it should typically be set to
# YES.
# The default value is: system dependent.
CASE_SENSE_NAMES = YES
# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
# their full class and namespace scopes in the documentation. If set to YES, the
# scope will be hidden.
# The default value is: NO.
HIDE_SCOPE_NAMES = NO
# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
# append additional text to a page's title, such as Class Reference. If set to
# YES the compound reference will be hidden.
# The default value is: NO.
HIDE_COMPOUND_REFERENCE= NO
# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
# will show which file needs to be included to use the class.
# The default value is: YES.
SHOW_HEADERFILE = YES
# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
# the files that are included by a file in the documentation of that file.
# The default value is: YES.
SHOW_INCLUDE_FILES = YES
# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
# grouped member an include statement to the documentation, telling the reader
# which file to include in order to use the member.
# The default value is: NO.
SHOW_GROUPED_MEMB_INC = NO
# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
# files with double quotes in the documentation rather than with sharp brackets.
# The default value is: NO.
FORCE_LOCAL_INCLUDES = NO
# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
# documentation for inline members.
# The default value is: YES.
INLINE_INFO = YES
# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
# (detailed) documentation of file and class members alphabetically by member
# name. If set to NO, the members will appear in declaration order.
# The default value is: YES.
SORT_MEMBER_DOCS = YES
# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
# descriptions of file, namespace and class members alphabetically by member
# name. If set to NO, the members will appear in declaration order. Note that
# this will also influence the order of the classes in the class list.
# The default value is: NO.
SORT_BRIEF_DOCS = NO
# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
# (brief and detailed) documentation of class members so that constructors and
# destructors are listed first. If set to NO the constructors will appear in the
# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
# member documentation.
# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
# detailed member documentation.
# The default value is: NO.
SORT_MEMBERS_CTORS_1ST = NO
# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
# of group names into alphabetical order. If set to NO the group names will
# appear in their defined order.
# The default value is: NO.
SORT_GROUP_NAMES = NO
# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
# fully-qualified names, including namespaces. If set to NO, the class list will
# be sorted only by class name, not including the namespace part.
# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
# Note: This option applies only to the class list, not to the alphabetical
# list.
# The default value is: NO.
SORT_BY_SCOPE_NAME = NO
# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
# type resolution of all parameters of a function it will reject a match between
# the prototype and the implementation of a member function even if there is
# only one candidate or it is obvious which candidate to choose by doing a
# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
# accept a match between prototype and implementation in such cases.
# The default value is: NO.
STRICT_PROTO_MATCHING = NO
# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
# list. This list is created by putting \todo commands in the documentation.
# The default value is: YES.
GENERATE_TODOLIST = YES
# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
# list. This list is created by putting \test commands in the documentation.
# The default value is: YES.
GENERATE_TESTLIST = YES
# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
# list. This list is created by putting \bug commands in the documentation.
# The default value is: YES.
GENERATE_BUGLIST = YES
# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
# the deprecated list. This list is created by putting \deprecated commands in
# the documentation.
# The default value is: YES.
GENERATE_DEPRECATEDLIST= YES
# The ENABLED_SECTIONS tag can be used to enable conditional documentation
# sections, marked by \if ... \endif and \cond
# ... \endcond blocks.
ENABLED_SECTIONS =
# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
# initial value of a variable or macro / define can have for it to appear in the
# documentation. If the initializer consists of more lines than specified here
# it will be hidden. Use a value of 0 to hide initializers completely. The
# appearance of the value of individual variables and macros / defines can be
# controlled using \showinitializer or \hideinitializer command in the
# documentation regardless of this setting.
# Minimum value: 0, maximum value: 10000, default value: 30.
MAX_INITIALIZER_LINES = 30
# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
# the bottom of the documentation of classes and structs. If set to YES, the
# list will mention the files that were used to generate the documentation.
# The default value is: YES.
SHOW_USED_FILES = YES
# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
# will remove the Files entry from the Quick Index and from the Folder Tree View
# (if specified).
# The default value is: YES.
SHOW_FILES = YES
# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
# page. This will remove the Namespaces entry from the Quick Index and from the
# Folder Tree View (if specified).
# The default value is: YES.
SHOW_NAMESPACES = YES
# The FILE_VERSION_FILTER tag can be used to specify a program or script that
# doxygen should invoke to get the current version for each file (typically from
# the version control system). Doxygen will invoke the program by executing (via
# popen()) the command command input-file, where command is the value of the
# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
# by doxygen. Whatever the program writes to standard output is used as the file
# version. For an example see the documentation.
FILE_VERSION_FILTER =
# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
# by doxygen. The layout file controls the global structure of the generated
# output files in an output format independent way. To create the layout file
# that represents doxygen's defaults, run doxygen with the -l option. You can
# optionally specify a file name after the option, if omitted DoxygenLayout.xml
# will be used as the name of the layout file. See also section "Changing the
# layout of pages" for information.
#
# Note that if you run doxygen from a directory containing a file called
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
# tag is left empty.
LAYOUT_FILE =
# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
# the reference definitions. This must be a list of .bib files. The .bib
# extension is automatically appended if omitted. This requires the bibtex tool
# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
# For LaTeX the style of the bibliography can be controlled using
# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
# search path. See also \cite for info how to create references.
CITE_BIB_FILES =
#---------------------------------------------------------------------------
# Configuration options related to warning and progress messages
#---------------------------------------------------------------------------
# The QUIET tag can be used to turn on/off the messages that are generated to
# standard output by doxygen. If QUIET is set to YES this implies that the
# messages are off.
# The default value is: NO.
QUIET = NO
# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
# this implies that the warnings are on.
#
# Tip: Turn warnings on while writing the documentation.
# The default value is: YES.
WARNINGS = YES
# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
# will automatically be disabled.
# The default value is: YES.
WARN_IF_UNDOCUMENTED = YES
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as documenting some parameters in
# a documented function twice, or documenting parameters that don't exist or
# using markup commands wrongly.
# The default value is: YES.
WARN_IF_DOC_ERROR = YES
# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
# function parameter documentation. If set to NO, doxygen will accept that some
# parameters have no documentation without warning.
# The default value is: YES.
WARN_IF_INCOMPLETE_DOC = YES
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
# are documented, but have no documentation for their parameters or return
# value. If set to NO, doxygen will only warn about wrong parameter
# documentation, but not about the absence of documentation. If EXTRACT_ALL is
# set to YES then this flag will automatically be disabled. See also
# WARN_IF_INCOMPLETE_DOC
# The default value is: NO.
WARN_NO_PARAMDOC = NO
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
# at the end of the doxygen process doxygen will return with a non-zero status.
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
# The default value is: NO.
WARN_AS_ERROR = NO
# The WARN_FORMAT tag determines the format of the warning messages that doxygen
# can produce. The string should contain the $file, $line, and $text tags, which
# will be replaced by the file and line number from which the warning originated
# and the warning text. Optionally the format may contain $version, which will
# be replaced by the version of the file (if it could be obtained via
# FILE_VERSION_FILTER)
# See also: WARN_LINE_FORMAT
# The default value is: $file:$line: $text.
WARN_FORMAT = "$file:$line: $text"
# In the $text part of the WARN_FORMAT command it is possible that a reference
# to a more specific place is given. To make it easier to jump to this place
# (outside of doxygen) the user can define a custom "cut" / "paste" string.
# Example:
# WARN_LINE_FORMAT = "'vi $file +$line'"
# See also: WARN_FORMAT
# The default value is: at line $line of file $file.
WARN_LINE_FORMAT = "at line $line of file $file"
# The WARN_LOGFILE tag can be used to specify a file to which warning and error
# messages should be written. If left blank the output is written to standard
# error (stderr). In case the file specified cannot be opened for writing the
# warning and error messages are written to standard error. When as file - is
# specified the warning and error messages are written to standard output
# (stdout).
WARN_LOGFILE =
#---------------------------------------------------------------------------
# Configuration options related to the input files
#---------------------------------------------------------------------------
# The INPUT tag is used to specify the files and/or directories that contain
# documented source files. You may enter file names like myfile.cpp or
# directories like /usr/src/myproject. Separate the files or directories with
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = src/ include/
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
# documentation (see:
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
# The default value is: UTF-8.
INPUT_ENCODING = UTF-8
# If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
# *.h) to filter out the source-files in the directories.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# read by doxygen.
#
# Note the list of default checked file patterns might differ from the list of
# default file extension mappings.
#
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
#FILE_PATTERNS = *.c *.h
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
# The default value is: NO.
RECURSIVE = YES
# The EXCLUDE tag can be used to specify files and/or directories that should be
# excluded from the INPUT source files. This way you can easily exclude a
# subdirectory from a directory tree whose root is specified with the INPUT tag.
#
# Note that relative paths are relative to the directory from which doxygen is
# run.
EXCLUDE =
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
# from the input.
# The default value is: NO.
EXCLUDE_SYMLINKS = NO
# If the value of the INPUT tag contains directories, you can use the
# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
# certain files from those directories.
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories for example use the pattern */test/*
EXCLUDE_PATTERNS = */tests/* */doc/*
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# ANamespace::AClass, ANamespace::*Test
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories use the pattern */test/*
EXCLUDE_SYMBOLS =
# The EXAMPLE_PATH tag can be used to specify one or more files or directories
# that contain example code fragments that are included (see the \include
# command).
EXAMPLE_PATH =
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
# *.h) to filter out the source-files in the directories. If left blank all
# files are included.
EXAMPLE_PATTERNS = *
# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
# searched for input files to be used with the \include or \dontinclude commands
# irrespective of the value of the RECURSIVE tag.
# The default value is: NO.
EXAMPLE_RECURSIVE = NO
# The IMAGE_PATH tag can be used to specify one or more files or directories
# that contain images that are to be included in the documentation (see the
# \image command).
IMAGE_PATH =
# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program
# by executing (via popen()) the command:
#
#
#
# where is the value of the INPUT_FILTER tag, and is the
# name of an input file. Doxygen will then use the output that the filter
# program writes to standard output. If FILTER_PATTERNS is specified, this tag
# will be ignored.
#
# Note that the filter must not add or remove lines; it is applied before the
# code is scanned, but not when the output code is generated. If lines are added
# or removed, the anchors will not be placed correctly.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.
INPUT_FILTER =
# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
# basis. Doxygen will compare the file name with each pattern and apply the
# filter if there is a match. The filters are a list of the form: pattern=filter
# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
# patterns match the file name, INPUT_FILTER is applied.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.
FILTER_PATTERNS =
# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
# INPUT_FILTER) will also be used to filter the input files that are used for
# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
# The default value is: NO.
FILTER_SOURCE_FILES = NO
# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
# it is also possible to disable source filtering for a specific pattern using
# *.ext= (so without naming a filter).
# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
FILTER_SOURCE_PATTERNS =
# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
# is part of the input, its contents will be placed on the main page
# (index.html). This can be useful if you have a project on for instance GitHub
# and want to reuse the introduction page also for the doxygen output.
USE_MDFILE_AS_MAINPAGE =
#---------------------------------------------------------------------------
# Configuration options related to source browsing
#---------------------------------------------------------------------------
# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
# generated. Documented entities will be cross-referenced with these sources.
#
# Note: To get rid of all source code in the generated output, make sure that
# also VERBATIM_HEADERS is set to NO.
# The default value is: NO.
SOURCE_BROWSER = NO
# Setting the INLINE_SOURCES tag to YES will include the body of functions,
# classes and enums directly into the documentation.
# The default value is: NO.
INLINE_SOURCES = NO
# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
# special comment blocks from generated source code fragments. Normal C, C++ and
# Fortran comments will always remain visible.
# The default value is: YES.
STRIP_CODE_COMMENTS = YES
# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
# entity all documented functions referencing it will be listed.
# The default value is: NO.
REFERENCED_BY_RELATION = NO
# If the REFERENCES_RELATION tag is set to YES then for each documented function
# all documented entities called/used by that function will be listed.
# The default value is: NO.
REFERENCES_RELATION = NO
# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
# to YES then the hyperlinks from functions in REFERENCES_RELATION and
# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
# link to the documentation.
# The default value is: YES.
REFERENCES_LINK_SOURCE = YES
# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
# source code will show a tooltip with additional information such as prototype,
# brief description and links to the definition and documentation. Since this
# will make the HTML file larger and loading of large files a bit slower, you
# can opt to disable this feature.
# The default value is: YES.
# This tag requires that the tag SOURCE_BROWSER is set to YES.
SOURCE_TOOLTIPS = YES
# If the USE_HTAGS tag is set to YES then the references to source code will
# point to the HTML generated by the htags(1) tool instead of doxygen built-in
# source browser. The htags tool is part of GNU's global source tagging system
# (see https://www.gnu.org/software/global/global.html). You will need version
# 4.8.6 or higher.
#
# To use it do the following:
# - Install the latest version of global
# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
# - Make sure the INPUT points to the root of the source tree
# - Run doxygen as normal
#
# Doxygen will invoke htags (and that will in turn invoke gtags), so these
# tools must be available from the command line (i.e. in the search path).
#
# The result: instead of the source browser generated by doxygen, the links to
# source code will now point to the output of htags.
# The default value is: NO.
# This tag requires that the tag SOURCE_BROWSER is set to YES.
USE_HTAGS = NO
# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
# verbatim copy of the header file for each class for which an include is
# specified. Set to NO to disable this.
# See also: Section \class.
# The default value is: YES.
VERBATIM_HEADERS = YES
# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
# clang parser (see:
# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
# performance. This can be particularly helpful with template rich C++ code for
# which doxygen's built-in parser lacks the necessary type information.
# Note: The availability of this option depends on whether or not doxygen was
# generated with the -Duse_libclang=ON option for CMake.
# The default value is: NO.
CLANG_ASSISTED_PARSING = NO
# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
# tag is set to YES then doxygen will add the directory of each input to the
# include path.
# The default value is: YES.
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
CLANG_ADD_INC_PATHS = YES
# If clang assisted parsing is enabled you can provide the compiler with command
# line options that you would normally use when invoking the compiler. Note that
# the include paths will already be set by doxygen for the files and directories
# specified with INPUT and INCLUDE_PATH.
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
CLANG_OPTIONS =
# If clang assisted parsing is enabled you can provide the clang parser with the
# path to the directory containing a file called compile_commands.json. This
# file is the compilation database (see:
# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
# options used when the source files were built. This is equivalent to
# specifying the -p option to a clang tool, such as clang-check. These options
# will then be passed to the parser. Any options specified with CLANG_OPTIONS
# will be added as well.
# Note: The availability of this option depends on whether or not doxygen was
# generated with the -Duse_libclang=ON option for CMake.
CLANG_DATABASE_PATH =
#---------------------------------------------------------------------------
# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
# compounds will be generated. Enable this if the project contains a lot of
# classes, structs, unions or interfaces.
# The default value is: YES.
ALPHABETICAL_INDEX = YES
# In case all classes in a project start with a common prefix, all classes will
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
# can be used to specify a prefix (or a list of prefixes) that should be ignored
# while generating the index headers.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the HTML output
#---------------------------------------------------------------------------
# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
# The default value is: YES.
GENERATE_HTML = YES
# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: html.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_OUTPUT = html
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
# generated HTML page (for example: .htm, .php, .asp).
# The default value is: .html.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FILE_EXTENSION = .html
# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
# each generated HTML page. If the tag is left blank doxygen will generate a
# standard header.
#
# To get valid HTML the header file that includes any scripts and style sheets
# that doxygen needs, which is dependent on the configuration options used (e.g.
# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
# default header using
# doxygen -w html new_header.html new_footer.html new_stylesheet.css
# YourConfigFile
# and then modify the file new_header.html. See also section "Doxygen usage"
# for information on how to generate the default header that doxygen normally
# uses.
# Note: The header is subject to change so you typically have to regenerate the
# default header when upgrading to a newer version of doxygen. For a description
# of the possible markers and block names see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_HEADER =
# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
# generated HTML page. If the tag is left blank doxygen will generate a standard
# footer. See HTML_HEADER for more information on how to generate a default
# footer and what special commands can be used inside the footer. See also
# section "Doxygen usage" for information on how to generate the default footer
# that doxygen normally uses.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FOOTER =
# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
# sheet that is used by each HTML page. It can be used to fine-tune the look of
# the HTML output. If left blank doxygen will generate a default style sheet.
# See also section "Doxygen usage" for information on how to generate the style
# sheet that doxygen normally uses.
# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
# it is more robust and this tag (HTML_STYLESHEET) will in the future become
# obsolete.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_STYLESHEET =
# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# cascading style sheets that are included after the standard style sheets
# created by doxygen. Using this option one can overrule certain style aspects.
# This is preferred over using HTML_STYLESHEET since it does not replace the
# standard style sheet and is therefore more robust against future updates.
# Doxygen will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the
# list). For an example see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET =
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
# that these files will be copied to the base HTML output directory. Use the
# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
# files will be copied as-is; there are no commands or markers available.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_FILES =
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to
# this color. Hue is specified as an angle on a color-wheel, see
# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
# purple, and 360 is red again.
# Minimum value: 0, maximum value: 359, default value: 220.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE_HUE = 220
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
# in the HTML output. For a value of 0 the output will use gray-scales only. A
# value of 255 will produce the most vivid colors.
# Minimum value: 0, maximum value: 255, default value: 100.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE_SAT = 100
# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
# luminance component of the colors in the HTML output. Values below 100
# gradually make the output lighter, whereas values above 100 make the output
# darker. The value divided by 100 is the actual gamma applied, so 80 represents
# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
# change the gamma.
# Minimum value: 40, maximum value: 240, default value: 80.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE_GAMMA = 80
# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
# page will contain the date and time when the page was generated. Setting this
# to YES can help to show when doxygen was last run and thus if the
# documentation is up to date.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_TIMESTAMP = NO
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
# documentation will contain a main index with vertical navigation menus that
# are dynamically created via JavaScript. If disabled, the navigation index will
# consists of multiple levels of tabs that are statically embedded in every HTML
# page. Disable this option to support browsers that do not have JavaScript,
# like the Qt help browser.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_DYNAMIC_MENUS = YES
# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
# documentation will contain sections that can be hidden and shown after the
# page has loaded.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_DYNAMIC_SECTIONS = NO
# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
# shown in the various tree structured indices initially; the user can expand
# and collapse entries dynamically later on. Doxygen will expand the tree to
# such a level that at most the specified number of entries are visible (unless
# a fully collapsed tree already exceeds this amount). So setting the number of
# entries 1 will produce a full collapsed tree by default. 0 is a special value
# representing an infinite number of entries and will result in a full expanded
# tree by default.
# Minimum value: 0, maximum value: 9999, default value: 100.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_INDEX_NUM_ENTRIES = 100
# If the GENERATE_DOCSET tag is set to YES, additional index files will be
# generated that can be used as input for Apple's Xcode 3 integrated development
# environment (see:
# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
# create a documentation set, doxygen will generate a Makefile in the HTML
# output directory. Running make will produce the docset in that directory and
# running make install will install the docset in
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
# genXcode/_index.html for more information.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_DOCSET = NO
# This tag determines the name of the docset feed. A documentation feed provides
# an umbrella under which multiple documentation sets from a single provider
# (such as a company or product suite) can be grouped.
# The default value is: Doxygen generated docs.
# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_FEEDNAME = "Doxygen generated docs"
# This tag determines the URL of the docset feed. A documentation feed provides
# an umbrella under which multiple documentation sets from a single provider
# (such as a company or product suite) can be grouped.
# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_FEEDURL =
# This tag specifies a string that should uniquely identify the documentation
# set bundle. This should be a reverse domain-name style string, e.g.
# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_BUNDLE_ID = org.doxygen.Project
# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
# the documentation publisher. This should be a reverse domain-name style
# string, e.g. com.mycompany.MyDocSet.documentation.
# The default value is: org.doxygen.Publisher.
# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
# The default value is: Publisher.
# This tag requires that the tag GENERATE_DOCSET is set to YES.
DOCSET_PUBLISHER_NAME = Publisher
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
# on Windows. In the beginning of 2021 Microsoft took the original page, with
# a.o. the download links, offline the HTML help workshop was already many years
# in maintenance mode). You can download the HTML help workshop from the web
# archives at Installation executable (see:
# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
#
# The HTML Help Workshop contains a compiler that can convert all HTML output
# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
# files are now used as the Windows 98 help format, and will replace the old
# Windows help format (.hlp) on all Windows platforms in the future. Compressed
# HTML files also contain an index, a table of contents, and you can search for
# words in the documentation. The HTML workshop also contains a viewer for
# compressed HTML files.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_HTMLHELP = NO
# The CHM_FILE tag can be used to specify the file name of the resulting .chm
# file. You can add a path in front of the file if the result should not be
# written to the html output directory.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
CHM_FILE =
# The HHC_LOCATION tag can be used to specify the location (absolute path
# including file name) of the HTML help compiler (hhc.exe). If non-empty,
# doxygen will try to run the HTML help compiler on the generated index.hhp.
# The file has to be specified with full path.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
HHC_LOCATION =
# The GENERATE_CHI flag controls if a separate .chi index file is generated
# (YES) or that it should be included in the main .chm file (NO).
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
GENERATE_CHI = NO
# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
# and project file content.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
CHM_INDEX_ENCODING =
# The BINARY_TOC flag controls whether a binary table of contents is generated
# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
# enables the Previous and Next buttons.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
BINARY_TOC = NO
# The TOC_EXPAND flag can be set to YES to add extra items for group members to
# the table of contents of the HTML help documentation and to the tree view.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
TOC_EXPAND = NO
# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
# (.qch) of the generated HTML documentation.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_QHP = NO
# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
# the file name of the resulting .qch file. The path specified is relative to
# the HTML output folder.
# This tag requires that the tag GENERATE_QHP is set to YES.
QCH_FILE =
# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
# Project output. For more information please see Qt Help Project / Namespace
# (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_NAMESPACE = org.doxygen.Project
# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
# Help Project output. For more information please see Qt Help Project / Virtual
# Folders (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
# The default value is: doc.
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_VIRTUAL_FOLDER = doc
# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
# filter to add. For more information please see Qt Help Project / Custom
# Filters (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_NAME =
# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
# custom filter to add. For more information please see Qt Help Project / Custom
# Filters (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_ATTRS =
# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
# project's filter section matches. Qt Help Project / Filter Attributes (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_SECT_FILTER_ATTRS =
# The QHG_LOCATION tag can be used to specify the location (absolute path
# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
# run qhelpgenerator on the generated .qhp file.
# This tag requires that the tag GENERATE_QHP is set to YES.
QHG_LOCATION =
# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
# generated, together with the HTML files, they form an Eclipse help plugin. To
# install this plugin and make it available under the help contents menu in
# Eclipse, the contents of the directory containing the HTML and XML files needs
# to be copied into the plugins directory of eclipse. The name of the directory
# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
# After copying Eclipse needs to be restarted before the help appears.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_ECLIPSEHELP = NO
# A unique identifier for the Eclipse help plugin. When installing the plugin
# the directory name containing the HTML and XML files should also have this
# name. Each documentation set should have its own identifier.
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
ECLIPSE_DOC_ID = org.doxygen.Project
# If you want full control over the layout of the generated HTML pages it might
# be necessary to disable the index and replace it with your own. The
# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
# of each HTML page. A value of NO enables the index and the value YES disables
# it. Since the tabs in the index contain the same information as the navigation
# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
DISABLE_INDEX = NO
# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
# structure should be generated to display hierarchical information. If the tag
# value is set to YES, a side panel will be generated containing a tree-like
# index structure (just like the one that is generated for HTML Help). For this
# to work a browser that supports JavaScript, DHTML, CSS and frames is required
# (i.e. any modern browser). Windows users are probably better off using the
# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
# further fine tune the look of the index (see "Fine-tuning the output"). As an
# example, the default style sheet generated by doxygen has an example that
# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
# Since the tree basically has the same information as the tab index, you could
# consider setting DISABLE_INDEX to YES when enabling this option.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_TREEVIEW = NO
# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
# area (value NO) or if it should extend to the full height of the window (value
# YES). Setting this to YES gives a layout similar to
# https://docs.readthedocs.io with more room for contents, but less room for the
# project logo, title, and description. If either GENERATE_TREEVIEW or
# DISABLE_INDEX is set to NO, this option has no effect.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
FULL_SIDEBAR = NO
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
# doxygen will group on one line in the generated HTML documentation.
#
# Note that a value of 0 will completely suppress the enum values from appearing
# in the overview section.
# Minimum value: 0, maximum value: 20, default value: 4.
# This tag requires that the tag GENERATE_HTML is set to YES.
ENUM_VALUES_PER_LINE = 4
# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
# to set the initial width (in pixels) of the frame in which the tree is shown.
# Minimum value: 0, maximum value: 1500, default value: 250.
# This tag requires that the tag GENERATE_HTML is set to YES.
TREEVIEW_WIDTH = 250
# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
# external symbols imported via tag files in a separate window.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
EXT_LINKS_IN_WINDOW = NO
# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
# addresses.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
OBFUSCATE_EMAILS = YES
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
# the HTML output. These images will generally look nicer at scaled resolutions.
# Possible values are: png (the default) and svg (looks nicer but requires the
# pdf2svg or inkscape tool).
# The default value is: png.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FORMULA_FORMAT = png
# Use this tag to change the font size of LaTeX formulas included as images in
# the HTML documentation. When you change the font size after a successful
# doxygen run you need to manually remove any form_*.png images from the HTML
# output directory to force them to be regenerated.
# Minimum value: 8, maximum value: 50, default value: 10.
# This tag requires that the tag GENERATE_HTML is set to YES.
FORMULA_FONTSIZE = 10
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
# generated for formulas are transparent PNGs. Transparent PNGs are not
# supported properly for IE 6.0, but are supported on all modern browsers.
#
# Note that when changing this option you need to delete any form_*.png files in
# the HTML output directory before the changes have effect.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
FORMULA_TRANSPARENT = YES
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
# to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details.
FORMULA_MACROFILE =
# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
# https://www.mathjax.org) which uses client side JavaScript for the rendering
# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
# installed or if you want to formulas look prettier in the HTML output. When
# enabled you may also need to install MathJax separately and configure the path
# to it using the MATHJAX_RELPATH option.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
USE_MATHJAX = NO
# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
# Note that the different versions of MathJax have different requirements with
# regards to the different settings, so it is possible that also other MathJax
# settings have to be changed when switching between the different MathJax
# versions.
# Possible values are: MathJax_2 and MathJax_3.
# The default value is: MathJax_2.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_VERSION = MathJax_2
# When MathJax is enabled you can set the default output format to be used for
# the MathJax output. For more details about the output format see MathJax
# version 2 (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
# (see:
# http://docs.mathjax.org/en/latest/web/components/output.html).
# Possible values are: HTML-CSS (which is slower, but has the best
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
# is the name for Mathjax version 3, for MathJax version 2 this will be
# translated into HTML-CSS) and SVG.
# The default value is: HTML-CSS.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_FORMAT = HTML-CSS
# When MathJax is enabled you need to specify the location relative to the HTML
# output directory using the MATHJAX_RELPATH option. The destination directory
# should contain the MathJax.js script. For instance, if the mathjax directory
# is located at the same level as the HTML output directory, then
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment. The default value is:
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH =
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
# extension names that should be enabled during MathJax rendering. For example
# for MathJax version 2 (see
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
# For example for MathJax version 3 (see
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
# MATHJAX_EXTENSIONS = ams
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_EXTENSIONS =
# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
# of code that will be used on startup of the MathJax code. See the MathJax site
# (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
# example see the documentation.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_CODEFILE =
# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
# the HTML output. The underlying search engine uses javascript and DHTML and
# should work on any modern browser. Note that when using HTML help
# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
# there is already a search function so this one should typically be disabled.
# For large projects the javascript based search engine can be slow, then
# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
# search using the keyboard; to jump to the search box use + S
# (what the is depends on the OS and browser, but it is typically
# , /, or both). Inside the search box use the to jump into the search results window, the results can be navigated
# using the . Press to select an item or to cancel
# the search. The filter options can be selected when the cursor is inside the
# search box by pressing +. Also here use the
# to select a filter and or to activate or cancel the filter
# option.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
SEARCHENGINE = YES
# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
# implemented using a web server instead of a web client using JavaScript. There
# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
# setting. When disabled, doxygen will generate a PHP script for searching and
# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
# and searching needs to be provided by external tools. See the section
# "External Indexing and Searching" for details.
# The default value is: NO.
# This tag requires that the tag SEARCHENGINE is set to YES.
SERVER_BASED_SEARCH = NO
# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
# script for searching. Instead the search results are written to an XML file
# which needs to be processed by an external indexer. Doxygen will invoke an
# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
# search results.
#
# Doxygen ships with an example indexer (doxyindexer) and search engine
# (doxysearch.cgi) which are based on the open source search engine library
# Xapian (see:
# https://xapian.org/).
#
# See the section "External Indexing and Searching" for details.
# The default value is: NO.
# This tag requires that the tag SEARCHENGINE is set to YES.
EXTERNAL_SEARCH = NO
# The SEARCHENGINE_URL should point to a search engine hosted by a web server
# which will return the search results when EXTERNAL_SEARCH is enabled.
#
# Doxygen ships with an example indexer (doxyindexer) and search engine
# (doxysearch.cgi) which are based on the open source search engine library
# Xapian (see:
# https://xapian.org/). See the section "External Indexing and Searching" for
# details.
# This tag requires that the tag SEARCHENGINE is set to YES.
SEARCHENGINE_URL =
# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
# search data is written to a file for indexing by an external tool. With the
# SEARCHDATA_FILE tag the name of this file can be specified.
# The default file is: searchdata.xml.
# This tag requires that the tag SEARCHENGINE is set to YES.
SEARCHDATA_FILE = searchdata.xml
# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
# projects and redirect the results back to the right project.
# This tag requires that the tag SEARCHENGINE is set to YES.
EXTERNAL_SEARCH_ID =
# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
# projects other than the one defined by this configuration file, but that are
# all added to the same external search index. Each project needs to have a
# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
# to a relative location where the documentation can be found. The format is:
# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
# This tag requires that the tag SEARCHENGINE is set to YES.
EXTRA_SEARCH_MAPPINGS =
#---------------------------------------------------------------------------
# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------
# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.
GENERATE_LATEX = NO
# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: latex.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_OUTPUT = latex
# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked.
#
# Note that when not enabling USE_PDFLATEX the default is latex when enabling
# USE_PDFLATEX the default is pdflatex and when in the later case latex is
# chosen this is overwritten by pdflatex. For specific output languages the
# default can have been set differently, this depends on the implementation of
# the output language.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_CMD_NAME =
# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
# index for LaTeX.
# Note: This tag is used in the Makefile / make.bat.
# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
# (.tex).
# The default file is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES.
MAKEINDEX_CMD_NAME = makeindex
# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
# generate index for LaTeX. In case there is no backslash (\) as first character
# it will be automatically added in the LaTeX code.
# Note: This tag is used in the generated output file (.tex).
# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
# The default value is: makeindex.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_MAKEINDEX_CMD = makeindex
# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
# documents. This may be useful for small projects and may help to save some
# trees in general.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
COMPACT_LATEX = NO
# The PAPER_TYPE tag can be used to set the paper type that is used by the
# printer.
# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
# 14 inches) and executive (7.25 x 10.5 inches).
# The default value is: a4.
# This tag requires that the tag GENERATE_LATEX is set to YES.
PAPER_TYPE = a4
# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
# that should be included in the LaTeX output. The package can be specified just
# by its name or with the correct syntax as to be used with the LaTeX
# \usepackage command. To get the times font for instance you can specify :
# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
# To use the option intlimits with the amsmath package you can specify:
# EXTRA_PACKAGES=[intlimits]{amsmath}
# If left blank no extra packages will be included.
# This tag requires that the tag GENERATE_LATEX is set to YES.
EXTRA_PACKAGES =
# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
# the generated LaTeX document. The header should contain everything until the
# first chapter. If it is left blank doxygen will generate a standard header. It
# is highly recommended to start with a default header using
# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
# and then modify the file new_header.tex. See also section "Doxygen usage" for
# information on how to generate the default header that doxygen normally uses.
#
# Note: Only use a user-defined header if you know what you are doing!
# Note: The header is subject to change so you typically have to regenerate the
# default header when upgrading to a newer version of doxygen. The following
# commands have a special meaning inside the header (and footer): For a
# description of the possible markers and block names see the documentation.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HEADER =
# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
# the generated LaTeX document. The footer should contain everything after the
# last chapter. If it is left blank doxygen will generate a standard footer. See
# LATEX_HEADER for more information on how to generate a default footer and what
# special commands can be used inside the footer. See also section "Doxygen
# usage" for information on how to generate the default footer that doxygen
# normally uses. Note: Only use a user-defined footer if you know what you are
# doing!
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_FOOTER =
# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# LaTeX style sheets that are included after the standard style sheets created
# by doxygen. Using this option one can overrule certain style aspects. Doxygen
# will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the
# list).
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_EXTRA_STYLESHEET =
# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the LATEX_OUTPUT output
# directory. Note that the files will be copied as-is; there are no commands or
# markers available.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_EXTRA_FILES =
# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
# contain links (just like the HTML output) instead of page references. This
# makes the output suitable for online browsing using a PDF viewer.
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.
PDF_HYPERLINKS = YES
# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
# files. Set this option to YES, to get a higher quality PDF documentation.
#
# See also section LATEX_CMD_NAME for selecting the engine.
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.
USE_PDFLATEX = YES
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
# command to the generated LaTeX files. This will instruct LaTeX to keep running
# if errors occur, instead of asking the user for help.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_BATCHMODE = NO
# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
# index chapters (such as File Index, Compound Index, etc.) in the output.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HIDE_INDICES = NO
# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
# bibliography, e.g. plainnat, or ieeetr. See
# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
# The default value is: plain.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_BIB_STYLE = plain
# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
# page will contain the date and time when the page was generated. Setting this
# to NO can help when comparing the output of multiple runs.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_TIMESTAMP = NO
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
# path from which the emoji images will be read. If a relative path is entered,
# it will be relative to the LATEX_OUTPUT directory. If left blank the
# LATEX_OUTPUT directory will be used.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_EMOJI_DIRECTORY =
#---------------------------------------------------------------------------
# Configuration options related to the RTF output
#---------------------------------------------------------------------------
# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
# RTF output is optimized for Word 97 and may not look too pretty with other RTF
# readers/editors.
# The default value is: NO.
GENERATE_RTF = NO
# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: rtf.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_OUTPUT = rtf
# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
# documents. This may be useful for small projects and may help to save some
# trees in general.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.
COMPACT_RTF = NO
# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
# contain hyperlink fields. The RTF file will contain links (just like the HTML
# output) instead of page references. This makes the output suitable for online
# browsing using Word or some other Word compatible readers that support those
# fields.
#
# Note: WordPad (write) and others do not support links.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_HYPERLINKS = NO
# Load stylesheet definitions from file. Syntax is similar to doxygen's
# configuration file, i.e. a series of assignments. You only have to provide
# replacements, missing definitions are set to their default value.
#
# See also section "Doxygen usage" for information on how to generate the
# default style sheet that doxygen normally uses.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_STYLESHEET_FILE =
# Set optional variables used in the generation of an RTF document. Syntax is
# similar to doxygen's configuration file. A template extensions file can be
# generated using doxygen -e rtf extensionFile.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
# Configuration options related to the man page output
#---------------------------------------------------------------------------
# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
# classes and files.
# The default value is: NO.
GENERATE_MAN = NO
# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it. A directory man3 will be created inside the directory specified by
# MAN_OUTPUT.
# The default directory is: man.
# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_OUTPUT = man
# The MAN_EXTENSION tag determines the extension that is added to the generated
# man pages. In case the manual section does not start with a number, the number
# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
# optional.
# The default value is: .3.
# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_EXTENSION = .3
# The MAN_SUBDIR tag determines the name of the directory created within
# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
# MAN_EXTENSION with the initial . removed.
# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_SUBDIR =
# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
# will generate one additional man file for each entity documented in the real
# man page(s). These additional files only source the real man page, but without
# them the man command would be unable to find the correct page.
# The default value is: NO.
# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_LINKS = NO
#---------------------------------------------------------------------------
# Configuration options related to the XML output
#---------------------------------------------------------------------------
# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
# captures the structure of the code including all documentation.
# The default value is: NO.
GENERATE_XML = YES
# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
# it.
# The default directory is: xml.
# This tag requires that the tag GENERATE_XML is set to YES.
XML_OUTPUT = doxyxml
# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
# listings (including syntax highlighting and cross-referencing information) to
# the XML output. Note that enabling this will significantly increase the size
# of the XML output.
# The default value is: YES.
# This tag requires that the tag GENERATE_XML is set to YES.
XML_PROGRAMLISTING = YES
# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
# namespace members in file scope as well, matching the HTML output.
# The default value is: NO.
# This tag requires that the tag GENERATE_XML is set to YES.
XML_NS_MEMB_FILE_SCOPE = NO
#---------------------------------------------------------------------------
# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------
# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
# that can be used to generate PDF.
# The default value is: NO.
GENERATE_DOCBOOK = NO
# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
# front of it.
# The default directory is: docbook.
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
DOCBOOK_OUTPUT = docbook
#---------------------------------------------------------------------------
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
# the structure of the code including all documentation. Note that this feature
# is still experimental and incomplete at the moment.
# The default value is: NO.
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
# file that captures the structure of the code including all documentation.
#
# Note that this feature is still experimental and incomplete at the moment.
# The default value is: NO.
GENERATE_PERLMOD = NO
# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
# output from the Perl module output.
# The default value is: NO.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.
PERLMOD_LATEX = NO
# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
# formatted so it can be parsed by a human reader. This is useful if you want to
# understand what is going on. On the other hand, if this tag is set to NO, the
# size of the Perl module output will be much smaller and Perl will parse it
# just the same.
# The default value is: YES.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.
PERLMOD_PRETTY = YES
# The names of the make variables in the generated doxyrules.make file are
# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
# so different doxyrules.make files included by the same Makefile don't
# overwrite each other's variables.
# This tag requires that the tag GENERATE_PERLMOD is set to YES.
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
# C-preprocessor directives found in the sources and include files.
# The default value is: YES.
ENABLE_PREPROCESSING = YES
# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
# in the source code. If set to NO, only conditional compilation will be
# performed. Macro expansion can be done in a controlled way by setting
# EXPAND_ONLY_PREDEF to YES.
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
MACRO_EXPANSION = NO
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
# the macro expansion is limited to the macros specified with the PREDEFINED and
# EXPAND_AS_DEFINED tags.
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
EXPAND_ONLY_PREDEF = NO
# If the SEARCH_INCLUDES tag is set to YES, the include files in the
# INCLUDE_PATH will be searched if a #include is found.
# The default value is: YES.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
SEARCH_INCLUDES = YES
# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by the
# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
# RECURSIVE has no effect here.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
INCLUDE_PATH =
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
# patterns (like *.h and *.hpp) to filter out the header-files in the
# directories. If left blank, the patterns specified with FILE_PATTERNS will be
# used.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
INCLUDE_FILE_PATTERNS =
# The PREDEFINED tag can be used to specify one or more macro names that are
# defined before the preprocessor is started (similar to the -D option of e.g.
# gcc). The argument of the tag is a list of macros of the form: name or
# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
# is assumed. To prevent a macro definition from being undefined via #undef or
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
PREDEFINED =
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
# macro definition that is found in the sources will be used. Use the PREDEFINED
# tag if you want to use a different macro definition that overrules the
# definition found in the source code.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
EXPAND_AS_DEFINED =
# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
# remove all references to function-like macros that are alone on a line, have
# an all uppercase name, and do not end with a semicolon. Such function macros
# are typically used for boiler-plate code, and will confuse the parser if not
# removed.
# The default value is: YES.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration options related to external references
#---------------------------------------------------------------------------
# The TAGFILES tag can be used to specify one or more tag files. For each tag
# file the location of the external documentation should be added. The format of
# a tag file without this location is as follows:
# TAGFILES = file1 file2 ...
# Adding location for the tag files is done as follows:
# TAGFILES = file1=loc1 "file2 = loc2" ...
# where loc1 and loc2 can be relative or absolute paths or URLs. See the
# section "Linking to external documentation" for more information about the use
# of tag files.
# Note: Each tag file must have a unique name (where the name does NOT include
# the path). If a tag file is not located in the directory in which doxygen is
# run, you must also specify the path to the tagfile here.
TAGFILES =
# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
# tag file that is based on the input files it reads. See section "Linking to
# external documentation" for more information about the usage of tag files.
GENERATE_TAGFILE =
# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
# the class index. If set to NO, only the inherited external classes will be
# listed.
# The default value is: NO.
ALLEXTERNALS = NO
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
# in the modules index. If set to NO, only the current project's groups will be
# listed.
# The default value is: YES.
EXTERNAL_GROUPS = YES
# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
# the related pages index. If set to NO, only the current project's pages will
# be listed.
# The default value is: YES.
EXTERNAL_PAGES = YES
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
# If left empty dia is assumed to be found in the default search path.
DIA_PATH =
# If set to YES the inheritance and collaboration graphs will hide inheritance
# and usage relations if the target is undocumented or is not a class.
# The default value is: YES.
HIDE_UNDOC_RELATIONS = YES
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
# available from the path. This tool is part of Graphviz (see:
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
# Bell Labs. The other options in this section have no effect if this option is
# set to NO
# The default value is: YES.
HAVE_DOT = YES
# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
# to run in parallel. When set to 0 doxygen will base this on the number of
# processors available in the system. You can set it explicitly to a value
# larger than 0 to get control over the balance between CPU load and processing
# speed.
# Minimum value: 0, maximum value: 32, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_NUM_THREADS = 0
# When you want a differently looking font in the dot files that doxygen
# generates you can specify the font name using DOT_FONTNAME. You need to make
# sure dot is able to find the font, which can be done by putting it in a
# standard location or by setting the DOTFONTPATH environment variable or by
# setting DOT_FONTPATH to the directory containing the font.
# The default value is: Helvetica.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTNAME = Helvetica
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
# dot graphs.
# Minimum value: 4, maximum value: 24, default value: 10.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTSIZE = 10
# By default doxygen will tell dot to use the default font as specified with
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
# the path where dot can find it using this tag.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTPATH =
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
# graph for each documented class showing the direct and indirect inheritance
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
# to TEXT the direct and indirect inheritance relations will be shown as texts /
# links.
# Possible values are: NO, YES, TEXT and GRAPH.
# The default value is: YES.
CLASS_GRAPH = YES
# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
# graph for each documented class showing the direct and indirect implementation
# dependencies (inheritance, containment, and class references variables) of the
# class with other documented classes.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
COLLABORATION_GRAPH = YES
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
# groups, showing the direct groups dependencies. See also the chapter Grouping
# in the manual.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
GROUP_GRAPHS = YES
# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
# collaboration diagrams in a style similar to the OMG's Unified Modeling
# Language.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
UML_LOOK = NO
# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
# class node. If there are many fields or methods and many nodes the graph may
# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
# number of items for each type to make the size more manageable. Set this to 0
# for no limit. Note that the threshold may be exceeded by 50% before the limit
# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
# but if the number exceeds 15, the total amount of fields shown is limited to
# 10.
# Minimum value: 0, maximum value: 100, default value: 10.
# This tag requires that the tag UML_LOOK is set to YES.
UML_LIMIT_NUM_FIELDS = 10
# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
# tag is set to YES, doxygen will add type and arguments for attributes and
# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
# will not generate fields with class member information in the UML graphs. The
# class diagrams will look similar to the default class diagrams but using UML
# notation for the relationships.
# Possible values are: NO, YES and NONE.
# The default value is: NO.
# This tag requires that the tag UML_LOOK is set to YES.
DOT_UML_DETAILS = NO
# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
# to display on a single line. If the actual line length exceeds this threshold
# significantly it will wrapped across multiple lines. Some heuristics are apply
# to avoid ugly line breaks.
# Minimum value: 0, maximum value: 1000, default value: 17.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_WRAP_THRESHOLD = 17
# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
# collaboration graphs will show the relations between templates and their
# instances.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
TEMPLATE_RELATIONS = NO
# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
# YES then doxygen will generate a graph for each documented file showing the
# direct and indirect include dependencies of the file with other documented
# files.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
INCLUDE_GRAPH = YES
# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
# set to YES then doxygen will generate a graph for each documented file showing
# the direct and indirect include dependencies of the file with other documented
# files.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
INCLUDED_BY_GRAPH = YES
# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
# dependency graph for every global function or class method.
#
# Note that enabling this option will significantly increase the time of a run.
# So in most cases it will be better to enable call graphs for selected
# functions only using the \callgraph command. Disabling a call graph can be
# accomplished by means of the command \hidecallgraph.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
CALL_GRAPH = NO
# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
# dependency graph for every global function or class method.
#
# Note that enabling this option will significantly increase the time of a run.
# So in most cases it will be better to enable caller graphs for selected
# functions only using the \callergraph command. Disabling a caller graph can be
# accomplished by means of the command \hidecallergraph.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
CALLER_GRAPH = NO
# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
# hierarchy of all classes instead of a textual one.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
GRAPHICAL_HIERARCHY = YES
# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
# dependencies a directory has on other directories in a graphical way. The
# dependency relations are determined by the #include relations between the
# files in the directories.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
DIRECTORY_GRAPH = YES
# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
# of child directories generated in directory dependency graphs by dot.
# Minimum value: 1, maximum value: 25, default value: 1.
# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
DIR_GRAPH_MAX_DEPTH = 1
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot. For an explanation of the image formats see the section
# output formats in the documentation of the dot tool (Graphviz (see:
# http://www.graphviz.org/)).
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
# to make the SVG files visible in IE 9+ (other browsers do not have this
# requirement).
# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd,
# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd,
# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
# png:gdiplus:gdiplus.
# The default value is: png.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_IMAGE_FORMAT = png
# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
# enable generation of interactive SVG images that allow zooming and panning.
#
# Note that this requires a modern browser other than Internet Explorer. Tested
# and working are Firefox, Chrome, Safari, and Opera.
# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
# the SVG files visible. Older versions of IE do not have SVG support.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
INTERACTIVE_SVG = NO
# The DOT_PATH tag can be used to specify the path where the dot tool can be
# found. If left blank, it is assumed the dot tool can be found in the path.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_PATH =
# The DOTFILE_DIRS tag can be used to specify one or more directories that
# contain dot files that are included in the documentation (see the \dotfile
# command).
# This tag requires that the tag HAVE_DOT is set to YES.
DOTFILE_DIRS =
# The MSCFILE_DIRS tag can be used to specify one or more directories that
# contain msc files that are included in the documentation (see the \mscfile
# command).
MSCFILE_DIRS =
# The DIAFILE_DIRS tag can be used to specify one or more directories that
# contain dia files that are included in the documentation (see the \diafile
# command).
DIAFILE_DIRS =
# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
# path where java can find the plantuml.jar file or to the filename of jar file
# to be used. If left blank, it is assumed PlantUML is not used or called during
# a preprocessing step. Doxygen will generate a warning when it encounters a
# \startuml command in this case and will not generate output for the diagram.
PLANTUML_JAR_PATH =
# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
# configuration file for plantuml.
PLANTUML_CFG_FILE =
# When using plantuml, the specified paths are searched for files specified by
# the !include statement in a plantuml block.
PLANTUML_INCLUDE_PATH =
# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
# that will be shown in the graph. If the number of nodes in a graph becomes
# larger than this value, doxygen will truncate the graph, which is visualized
# by representing a node as a red box. Note that doxygen if the number of direct
# children of the root node in a graph is already larger than
# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
# Minimum value: 0, maximum value: 10000, default value: 50.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_GRAPH_MAX_NODES = 50
# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
# generated by dot. A depth value of 3 means that only nodes reachable from the
# root by following a path via at most 3 edges will be shown. Nodes that lay
# further from the root node will be omitted. Note that setting this option to 1
# or 2 may greatly reduce the computation time needed for large code bases. Also
# note that the size of a graph can be further restricted by
# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
# Minimum value: 0, maximum value: 1000, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES.
MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
# background. This is disabled by default, because dot on Windows does not seem
# to support this out of the box.
#
# Warning: Depending on the platform used, enabling this option may lead to
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
# read).
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_TRANSPARENT = NO
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
# this, this feature is disabled by default.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_MULTI_TARGETS = NO
# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
# explaining the meaning of the various boxes and arrows in the dot generated
# graphs.
# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
# graphical representation for inheritance and collaboration diagrams is used.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
GENERATE_LEGEND = YES
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
# files that are used to generate the various graphs.
#
# Note: This setting is not only used for dot files but also for msc temporary
# files.
# The default value is: YES.
DOT_CLEANUP = YES
netplan-0.107.1/Makefile 0000664 0000000 0000000 00000002554 14536110317 0014770 0 ustar 00root root 0000000 0000000 .PHONY: clean default check linting pre-coverage
VER = $(shell meson introspect _build/ --projectinfo | jq -r '.version' && rm -rf _build)
DESTDIR ?= ../tmproot
default: _build
meson compile -C _build --verbose
_build:
meson setup _build --prefix=/usr
_build-cov:
meson setup _build-cov --prefix=/usr -Db_coverage=true
clean:
rm -f netplan_cli/_features.py src/_features.h src/_features.h.gch
rm -f generate doc/*.html doc/*.[1-9]
rm -f *.o *.so*
rm -f netplan-dbus dbus/*.service
rm -f *.gcda *.gcno generate.info
rm -f tests/ctests/*.gcda tests/ctests/*.gcno
rm -rf test-coverage .coverage
rm -f .coverage.* coverage.xml
find . | grep -E "(__pycache__|\.pyc)" | xargs rm -rf
rm -rf build
rm -rf _build
rm -rf _build-cov
rm -rf _leakcheckbuild
rm -rf tmproot
rm -f python-cffi/netplan/_netplan_cffi.*
dist: clean _build
tar --exclude="_build" --exclude=".git" --exclude="debian" --exclude=".vscode" -cvJf ../netplan-$(VER).tar.xz .
ln -sf netplan-$(VER).tar.xz ../netplan.io_$(VER).orig.tar.xz # Debian .orig symlink
check: default
meson test -C _build --verbose
linting: _build
meson test -C _build --verbose linting
meson test -C _build --verbose codestyle
pre-coverage: _build-cov
meson compile -C _build-cov --verbose
check-coverage: pre-coverage
meson test -C _build-cov --verbose
install: default
meson install -C _build --destdir $(DESTDIR)
netplan-0.107.1/README.md 0000664 0000000 0000000 00000003050 14536110317 0014577 0 ustar 00root root 0000000 0000000 # netplan - Backend-agnostic network configuration in YAML
[](https://github.com/canonical/netplan/actions/workflows/build-abi.yml?query=branch%3Amain)
[](https://github.com/canonical/netplan/actions/workflows/check-coverage.yml?query=branch%3Amain)
[](https://github.com/canonical/netplan/actions/workflows/autopkgtest.yml?query=branch%3Amain)
# Website
http://netplan.io
# Documentation
An overview of the architecture can be found at [netplan.io/design](https://netplan.io/design)
The full documentation for netplan is available in the [doc/](../main/doc/) directory.
# Build using Meson
Steps to build netplan using the [Meson](https://mesonbuild.com) build system inside the `build/` directory:
* meson setup build --prefix=/usr [-Db_coverage=true]
* meson compile -C build
* meson test -C build --verbose [TEST_NAME]
* meson install -C build --destdir ../tmproot
# Bug reports
Please file bug reports in [Launchpad](https://bugs.launchpad.net/netplan/+filebug).
# Contact us
Please join us on IRC in #netplan at [Libera.Chat](https://libera.chat/).
Our mailing list is [here](https://lists.launchpad.net/netplan-developers/).
Email the list at [netplan-developers@lists.launchpad.net](mailto:netplan-developers@lists.launchpad.net).
netplan-0.107.1/TODO 0000664 0000000 0000000 00000002526 14536110317 0014017 0 ustar 00root root 0000000 0000000 - improve IPv6 RA handling
- support tunnel device types
- support ethtool/sysctl knobs (TSO, LRO, txqueuelen)
- inspecting current network config via "netplan show $interface" for a
collated view of each interface's yaml.
- debugging config generation via "netplan diff [backend|system]":
- netplan diff system: compare generated config with current ip addr output
- netplan diff backend: compare generated config with current config for backend
- support other devices types from networkd/NetworkManager:
- infiniband
- veth
- better handle VLAN Q-in-Q (mostly generation tweaks + patching backends)
- support device aliases (eth0 + eth0.1; add eth0 to multiple bridges)
- workaround for two bridges is to use eth0 and vlan1
- make errors translatable
- "netplan save" to capture kernel state into netplan YAML.
- better parsing/validation for time-based values (ie. bond, bridge params)
- openvswitch integration
- wpa enterprise support
- better parsing/validation for all schema
- improve exit codes / behavior on error
- integrate 'netplan try' in tmux/screen
- add automated integration tests for WPA Enterprise / 802.1x that can run self-contained
# After soname bump (ABI break)
- get rid of abi_compat.c
- change route->scope to ENUM
- move tunnel_ttl into tunnel struct
- store match.driver as a list rather than a string
netplan-0.107.1/abi-compat/ 0000775 0000000 0000000 00000000000 14536110317 0015336 5 ustar 00root root 0000000 0000000 netplan-0.107.1/abi-compat/README.md 0000664 0000000 0000000 00000001315 14536110317 0016615 0 ustar 00root root 0000000 0000000 # Netplan's ABI checker
We're using "abigail" (abigail-tools) to validate libnetplan's ABI.
## HowTo create a ABI reference
The `abidw` tool can be used to generate an ABI XML like this:
```
meson setup _build --prefix=/usr
meson compile -C _build
abidw _build/src/libnetplan.so.0.0 --headers-dir include/ --header-file src/abi.h > abi-compat/jammy_0.107.xml
```
## HowTo compare a ABI
The `abidiff` tool can be used to compare a new library ABI to an existing XML
reference like this (also, see .github/workflows/build-abi.yml):
```
abidiff abi-compat/jammy_0.107.xml _build/src/libnetplan.so.0.0 --headers-dir2 include/ --header-file2 src/abi.h --suppressions abi-compat/suppressions.abignore --no-added-syms
```
netplan-0.107.1/abi-compat/jammy_0.107.xml 0000664 0000000 0000000 00001304760 14536110317 0017735 0 ustar 00root root 0000000 0000000
netplan-0.107.1/abi-compat/suppressions.abignore 0000664 0000000 0000000 00000000516 14536110317 0021625 0 ustar 00root root 0000000 0000000 # Ignore this type/file/function/variable during ABI checks
# passed to abidiff using the --suppressions parameter
#
# Documentation about the syntax can be found here:
# https://sourceware.org/libabigail/manual/libabigail-concepts.html#suppression-specifications
[suppress_variable]
name = global_state
type_name = NetplanState
netplan-0.107.1/abicompat.lds 0000664 0000000 0000000 00000000517 14536110317 0015770 0 ustar 00root root 0000000 0000000 SECTIONS
{
.data.abi_compat : {
netdefs = global_state + SIZEOF(netdefs_offset) - 8;
netdefs_ordered = global_state + SIZEOF(netdefs_ordered_offset) - 8;
ovs_settings_global = global_state + SIZEOF(ovs_settings_offset) - 8;
global_backend = global_state + SIZEOF(global_backend_offset) - 8;
}
}
INSERT AFTER .data;
netplan-0.107.1/dbus/ 0000775 0000000 0000000 00000000000 14536110317 0014257 5 ustar 00root root 0000000 0000000 netplan-0.107.1/dbus/io.netplan.Netplan.conf 0000664 0000000 0000000 00000001137 14536110317 0020577 0 ustar 00root root 0000000 0000000
netplan-0.107.1/dbus/io.netplan.Netplan.service.in 0000664 0000000 0000000 00000000175 14536110317 0021720 0 ustar 00root root 0000000 0000000 [D-BUS Service]
Name=io.netplan.Netplan
Exec=@ROOTLIBEXECDIR@/netplan/netplan-dbus
User=root
AssumedAppArmorLabel=unconfined
netplan-0.107.1/dbus/meson.build 0000664 0000000 0000000 00000001735 14536110317 0016427 0 ustar 00root root 0000000 0000000 features_h = custom_target(
build_always_stale: true,
output: '_features.h',
input: join_paths(meson.project_source_root(), 'features_h_generator.sh'),
command: ['sh', '-c', '@INPUT@'],
install: false,
capture: true,
)
executable(
'netplan-dbus',
'../src/dbus.c',
features_h,
include_directories: inc,
link_with: libnetplan,
dependencies: [libsystemd, glib, gio, yaml, uuid],
install_dir: join_paths(get_option('libexecdir'), 'netplan'),
install: true)
install_data(
'io.netplan.Netplan.conf',
install_dir: join_paths(get_option('datadir'), 'dbus-1', 'system.d'))
conf_data = configuration_data()
conf_data.set('ROOTLIBEXECDIR', join_paths(get_option('prefix'), get_option('libexecdir')))
configure_file(
input: 'io.netplan.Netplan.service.in',
output: 'io.netplan.Netplan.service',
configuration: conf_data,
install: true,
install_dir: join_paths(get_option('datadir'), 'dbus-1', 'system-services'))
netplan-0.107.1/doc/ 0000775 0000000 0000000 00000000000 14536110317 0014067 5 ustar 00root root 0000000 0000000 netplan-0.107.1/doc/.gitignore 0000664 0000000 0000000 00000000017 14536110317 0016055 0 ustar 00root root 0000000 0000000 _build
/*env*/
netplan-0.107.1/doc/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000020355 14536110317 0016673 0 ustar 00root root 0000000 0000000 # Ubuntu Code of Conduct v2.0
Ubuntu is about showing humanity to one another: the word itself captures the spirit of being human.
We want a productive, happy and agile community that can welcome new ideas in a complex field, improve every process every year, and foster collaboration between groups with very different needs, interests and skills.
We gain strength from diversity, and actively seek participation from those who enhance it. This code of conduct exists to ensure that diverse groups collaborate to mutual advantage and enjoyment. We will challenge prejudice that could jeopardise the participation of any person in the project.
The Code of Conduct governs how we behave in public or in private whenever the project will be judged by our actions. We expect it to be honoured by everyone who represents the project officially or informally, claims affiliation with the project, or participates directly.
## We strive to:
* Be considerate
Our work will be used by other people, and we in turn will depend on the work of others. Any decision we take will affect users and colleagues, and we should consider them when making decisions.
* Be respectful
Disagreement is no excuse for poor manners. We work together to resolve conflict, assume good intentions and do our best to act in an empathic fashion. We don’t allow frustration to turn into a personal attack. A community where people feel uncomfortable or threatened is not a productive one.
* Take responsibility for our words and our actions
We can all make mistakes; when we do, we take responsibility for them. If someone has been harmed or offended, we listen carefully and respectfully, and work to right the wrong.
* Be collaborative
What we produce is a complex whole made of many parts, it is the sum of many dreams. Collaboration between teams that each have their own goal and vision is essential; for the whole to be more than the sum of its parts, each part must make an effort to understand the whole.Collaboration reduces redundancy and improves the quality of our work. Internally and externally, we celebrate good collaboration. Wherever possible, we work closely with upstream projects and others in the free software community to coordinate our efforts. We prefer to work transparently and involve interested parties as early as possible.
* Value decisiveness, clarity and consensus
Disagreements, social and technical, are normal, but we do not allow them to persist and fester leaving others uncertain of the agreed direction.We expect participants in the project to resolve disagreements constructively. When they cannot, we escalate the matter to structures with designated leaders to arbitrate and provide clarity and direction.
* Ask for help when unsure
Nobody is expected to be perfect in this community. Asking questions early avoids many problems later, so questions are encouraged, though they may be directed to the appropriate forum. Those who are asked should be responsive and helpful.
* Step down considerately
When somebody leaves or disengages from the project, we ask that they do so in a way that minimises disruption to the project. They should tell people they are leaving and take the proper steps to ensure that others can pick up where they left off.
## Leadership, authority and responsibility
We all lead by example, in debate and in action. We encourage new participants to feel empowered to lead, to take action, and to experiment when they feel innovation could improve the project. Leadership can be exercised by anyone simply by taking action, there is no need to wait for recognition when the opportunity to lead presents itself.
### Delegation from the top
Responsibility for the project starts with the “benevolent dictator”, who delegates specific responsibilities and the corresponding authority to a series of teams, councils and individuals, starting with the Community Council (“CC”). That Council or its delegated representative will arbitrate in any dispute.
We are a meritocracy; we delegate decision making, governance and leadership from senior bodies to the most able and engaged candidates.
### Support for delegation is measured
Nominations to the boards and councils are at the discretion of the Community Council, however the Community Council will seek the input of the community before confirming appointments.
Leadership is not an award, right, or title; it is a privilege, a responsibility and a mandate. A leader will only retain their authority as long as they retain the support of those who delegated that authority to them.
### We value discussion, data and decisiveness
We gather opinions, data and commitments from concerned parties before taking a decision. We expect leaders to help teams come to a decision in a reasonable time, to seek guidance or be willing to take the decision themselves when consensus is lacking, and to take responsibility for implementation.
The poorest decision of all is no decision: clarity of direction has value in itself. Sometimes all the data are not available, or consensus is elusive. A decision must still be made. There is no guarantee of a perfect decision every time - we prefer to err, learn, and err less in future than to postpone action indefinitely.
We recognise that the project works better when we trust the teams closest to a problem to make the decision for the project. If we learn of a decision that we disagree with, we can engage the relevant team to find common ground, and failing that, we have a governance structure that can review the decision. Ultimately, if a decision has been taken by the people responsible for it, and is supported by the project governance, it will stand. None of us expects to agree with every decision, and we value highly the willingness to stand by the project and help it deliver even on the occasions when we ourselves may prefer a different route.
### Open meritocracy
We invite anybody, from any company, to participate in any aspect of the project. Our community is open, and any responsibility can be carried by any contributor who demonstrates the required capacity and competence.
### Teamwork
A leader’s foremost goal is the success of the team.
“A virtuoso is judged by their actions; a leader is judged by the actions of their team.” A leader knows when to act and when to step back. They know when to delegate work, and when to take it upon themselves.
### Credit
A good leader does not seek the limelight, but celebrates team members for the work they do. Leaders may be more visible than members of the team, good ones use that visibility to highlight the great work of others.
### Courage and considerateness
Leadership occasionally requires bold decisions that will not be widely understood, consensual or popular. We value the courage to take such decisions, because they enable the project as a whole to move forward faster than we could if we required complete consensus. Nevertheless, boldness demands considerateness; take bold decisions, but do so mindful of the challenges they present for others, and work to soften the impact of those decisions on them. Communicating changes and their reasoning clearly and early on is as important as the implementation of the change itself.
### Conflicts of interest
We expect leaders to be aware when they are conflicted due to employment or other projects they are involved in, and abstain or delegate decisions that may be seen to be self-interested. We expect that everyone who participates in the project does so with the goal of making life better for its users.
When in doubt, ask for a second opinion. Perceived conflicts of interest are important to address; as a leader, act to ensure that decisions are credible even if they must occasionally be unpopular, difficult or favourable to the interests of one group over another.
This Code is not exhaustive or complete. It is not a rulebook; it serves to distil our common understanding of a collaborative, shared environment and goals. We expect it to be followed in spirit as much as in the letter.
*The Ubuntu Code of Conduct is licensed under the [Creative Commons Attribution-Share Alike 3.0 license](https://creativecommons.org/licenses/by-sa/3.0/). You may re-use it for your own project, and modify it as you wish, just please allow others to use your modifications and give credit to the Ubuntu Project!*
netplan-0.107.1/doc/Makefile 0000664 0000000 0000000 00000003744 14536110317 0015537 0 ustar 00root root 0000000 0000000 # Minimal makefile for Sphinx documentation
# This is for development purposes only, e.g. to run a local Sphinx
# development environment. Usage:
#
# Setup: Run `make install` from doc/ to setup a Python virtual env
# Devel: Run `make run` from doc/ to get a live preview of your changes
# Test: Run `make spelling` from doc/ to validate your spelling
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
VENV = env/bin/activate
PORT = 8090
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
install:
@echo "... setting up virtualenv"
python3 -m venv env
. $(VENV); pip install --upgrade -r requirements.txt
@echo "\n" \
"--------------------------------------------------------------- \n" \
"* watch, build and serve the documentation: make run \n" \
"* check spelling: make spelling \n" \
"\n" \
"enchant must be installed in order for pyenchant (and therefore \n" \
"spelling checks) to work. \n" \
"--------------------------------------------------------------- \n"
clean:
-rm -rf _build/*
run:
. $(VENV); sphinx-autobuild $(ALLSPHINXOPTS) --ignore ".git/*" --ignore "*.scss" . -b dirhtml -a _build/html --host 0.0.0.0 --port $(PORT)
test:
. $(VENV); $(SPHINXBUILD) -b html . _build/html
html:
. $(VENV); $(SPHINXBUILD) -b dirhtml . _build/html
spelling:
. $(VENV); $(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) . _build/spelling
@echo
@echo "Check finished. Wrong words can be found in " \
"_build/spelling/output.txt."
quickstart:
. $(VENV); sphinx-quickstart
.PHONY: help install clean run Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
netplan-0.107.1/doc/README.md 0000777 0000000 0000000 00000000000 14536110317 0016772 2index.md ustar 00root root 0000000 0000000 netplan-0.107.1/doc/apidoc/ 0000775 0000000 0000000 00000000000 14536110317 0015326 5 ustar 00root root 0000000 0000000 netplan-0.107.1/doc/apidoc/inc-netplan.md 0000664 0000000 0000000 00000000070 14536110317 0020055 0 ustar 00root root 0000000 0000000 # netplan.h
```{autodoxygenfile} include/netplan.h
```
netplan-0.107.1/doc/apidoc/inc-parse-nm.md 0000664 0000000 0000000 00000000072 14536110317 0020140 0 ustar 00root root 0000000 0000000 # parse-nm.h
```{autodoxygenfile} include/parse-nm.h
```
netplan-0.107.1/doc/apidoc/inc-parse.md 0000664 0000000 0000000 00000000064 14536110317 0017531 0 ustar 00root root 0000000 0000000 # parse.h
```{autodoxygenfile} include/parse.h
```
netplan-0.107.1/doc/apidoc/inc-types.md 0000664 0000000 0000000 00000000064 14536110317 0017563 0 ustar 00root root 0000000 0000000 # types.h
```{autodoxygenfile} include/types.h
```
netplan-0.107.1/doc/apidoc/inc-util.md 0000664 0000000 0000000 00000000062 14536110317 0017372 0 ustar 00root root 0000000 0000000 # util.h
```{autodoxygenfile} include/util.h
```
netplan-0.107.1/doc/apidoc/index.md 0000664 0000000 0000000 00000001234 14536110317 0016757 0 ustar 00root root 0000000 0000000 # Reference: libnetplan API
## Public headers
```{toctree}
---
maxdepth: 1
---
inc-netplan
```
> ```{autodoxygenfile} include/netplan.h
> :sections: briefdescription
> ```
```{toctree}
---
maxdepth: 1
---
inc-parse-nm
```
> ```{autodoxygenfile} include/parse-nm.h
> :sections: briefdescription
> ```
```{toctree}
---
maxdepth: 1
---
inc-parse
```
> ```{autodoxygenfile} include/parse.h
> :sections: briefdescription
> ```
```{toctree}
---
maxdepth: 1
---
inc-types
```
> ```{autodoxygenfile} include/types.h
> :sections: briefdescription
> ```
```{toctree}
---
maxdepth: 1
---
inc-util
```
> ```{autodoxygenfile} include/util.h
> :sections: briefdescription
> ``` netplan-0.107.1/doc/cli.md 0000664 0000000 0000000 00000002435 14536110317 0015164 0 ustar 00root root 0000000 0000000 # Netplan CLI
```{toctree}
---
maxdepth: 1
hidden: true
---
generate
apply
try
get
set
info
ip
rebind
status
```
Netplan provides a command line interface, called `netplan`, which a user can
utilize to control certain aspects of the Netplan configuration.
| Tool | Description |
| --- | --- |
| help | Show a generic help message |
| [generate](/netplan-generate) | Generate backend specific configuration files from `/etc/netplan/*.yaml` |
| [apply](/netplan-apply) | Apply current netplan config to running system |
| [try](/netplan-try) | Try to apply a new netplan config to running system, with automatic rollback |
| [get](/netplan-get) | Get a setting by specifying a nested key like `"ethernets.eth0.addresses"`, or "all" |
| [set](/netplan-set) | Add new setting by specifying a dotted `key=value` pair like `"ethernets.eth0.dhcp4=true"` |
| [info](/netplan-info) | Show available features |
| [ip](/netplan-ip) | Retrieve IP information (like DHCP leases) from the system |
| [rebind](/netplan-rebind) | Rebind SR-IOV virtual functions of given physical functions to their driver |
| [status](/netplan-status) | Query networking state of the running system |
netplan-0.107.1/doc/conf.py 0000664 0000000 0000000 00000005500 14536110317 0015366 0 ustar 00root root 0000000 0000000 # Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html wokeignore:rule=master
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'Netplan'
copyright = '2022, Netplan team'
author = 'Netplan team'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx_design', 'myst_parser', 'sphinx_copybutton', 'sphinxcontrib.spelling', 'breathe']
myst_enable_extensions = ["colon_fence"]
smartquotes_action = 'qe'
# Doxygen
# https://breathe.readthedocs.io/en/latest/directives.html
# breathe_projects = {"Netplan": "../doxyxml/"}
breathe_projects_source = {"auto-apidoc": ("../", [
"include/netplan.h",
"include/parse-nm.h",
"include/parse.h",
"include/types.h",
"include/util.h",
"src/error.c",
"src/names.c",
"src/netplan.c",
"src/parse-nm.c",
"src/parse.c",
"src/types.c",
"src/util.c",
"src/validation.c",
])}
# breathe_doxygen_config_options =
# breathe_doxygen_aliases =
breathe_default_project = "auto-apidoc"
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'env']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'furo'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# The logo
html_logo = 'netplan.svg'
# -- Options for MyST --------------------------------------------------------
myst_title_to_header = True
suppress_warnings = ['myst.xref_missing']
# Spelling
spelling_lang = 'en_US'
tokenizer_lang = 'en_US'
spelling_show_suggestions = True
netplan-0.107.1/doc/contribute-docs.md 0000664 0000000 0000000 00000002151 14536110317 0017514 0 ustar 00root root 0000000 0000000 # Contribute Documentation
## Reporting an issue
If you find any issue in Netplan's documentation please [file a bugreport](https://bugs.launchpad.net/netplan/+filebug?field.tags=documentation)
about it in our bugtracker at Launchpad. Remember adding a `documentation` tag
to it.
## Modifying documentation online
Each documentation page, rendered on the web contains an "Edit this page" at
the top-right of every page, besides the documentation title. Clicking this
button will lead you to the GitHub web-editor where you can easily propose
changes to the corresponding page.
Please remember to first check the [latest version](https://netplan.readthedocs.io/en/latest/)
of our documentation and make your proposal based on that revision.
## Creating a pull-request
If you want to follow a git development workflow, you can also checkout the
[Netplan repository](https://github.com/canonical/netplan) and contribute your
changes as [pull-requests](https://github.com/canonical/netplan/pulls), putting
the `documentation` label for better visibility.
Please see the `doc/` and `examples/` directories for relevant files.
netplan-0.107.1/doc/dbus-config.md 0000664 0000000 0000000 00000004536 14536110317 0016621 0 ustar 00root root 0000000 0000000 # Use D-Bus config API
See also:
* [Netplan D-Bus reference](/netplan-dbus)
* [busctl reference](https://www.freedesktop.org/software/systemd/man/busctl.html)
Copy the current state from `/{etc,run,lib}/netplan/*.yaml` by creating a new config object
```
$ busctl call io.netplan.Netplan /io/netplan/Netplan io.netplan.Netplan Config
o "/io/netplan/Netplan/config/ULJIU0"
```
Read the merged YAML configuration
```
$ busctl call io.netplan.Netplan /io/netplan/Netplan/config/ULJIU0 io.netplan.Netplan.Config Get
s "network:\n ethernets:\n eth0:\n dhcp4: true\n renderer: networkd\n version: 2\n"
```
Write a new config snippet into `70-snapd.yaml`
```
$ busctl call io.netplan.Netplan /io/netplan/Netplan/config/ULJIU0 io.netplan.Netplan.Config Set ss "ethernets.eth0={dhcp4: false, dhcp6: true}" "70-snapd"
b true
```
Check the newly written configuration
```
$ busctl call io.netplan.Netplan /io/netplan/Netplan/config/ULJIU0 io.netplan.Netplan.Config Get
s "network:\n ethernets:\n eth0:\n dhcp4: false\n dhcp6: true\n renderer: networkd\n version: 2\n"
```
Try to apply the current config object's state
```
$ busctl call io.netplan.Netplan /io/netplan/Netplan/config/ULJIU0 io.netplan.Netplan.Config Try u 20
b true
```
Accept the Try() state within the 20 seconds timeout, if not it will be auto-rejected
```
$ busctl call io.netplan.Netplan /io/netplan/Netplan/config/ULJIU0 io.netplan.Netplan.Config Apply
b true
[SIGNAL] io.netplan.Netplan /io/netplan/Netplan/config/ULJIU0 io.netplan.Netplan.Config Changed() is triggered
[OBJECT] io.netplan.Netplan /io/netplan/Netplan/config/ULJIU0 is removed from the bus
```
Create a new config object and get the merged YAML config
```
$ busctl call io.netplan.Netplan /io/netplan/Netplan io.netplan.Netplan Config
o "/io/netplan/Netplan/config/KC0IU0
$ busctl call io.netplan.Netplan /io/netplan/Netplan/config/KC0IU0 io.netplan.Netplan.Config Get
s "network:\n ethernets:\n eth0:\n dhcp4: false\n dhcp6: true\n renderer: networkd\n version: 2\n"
```
Reject that config object again
```
$ busctl call io.netplan.Netplan /io/netplan/Netplan/config/KC0IU0 io.netplan.Netplan.Config Cancel
b true
[SIGNAL] io.netplan.Netplan /io/netplan/Netplan/config/KC0IU0 io.netplan.Netplan.Config Changed() is triggered
[OBJECT] io.netplan.Netplan /io/netplan/Netplan/config/KC0IU0 is removed from the bus
```
netplan-0.107.1/doc/example-config 0000664 0000000 0000000 00000003522 14536110317 0016712 0 ustar 00root root 0000000 0000000 network:
version: 2
# if specified, can only realistically have that value, as networkd cannot
# render wifi/3G. This would be shipped as a separate snippet by desktop images.
#renderer: NetworkManager
ethernets:
# opaque ID for physical interfaces, only referred to by other stanzas
id0:
match:
macaddress: 00:11:22:33:44:55
wakeonlan: true
dhcp4: true
addresses:
- 192.168.14.2/24
- "2001:1::1/64"
routes:
- to: default
via: 192.168.14.1
- to: default
via: "2001:1::2"
- to: 11.22.0.0/16
via: 192.168.14.3
metric: 100
nameservers:
search: [foo.local, bar.local]
addresses: [8.8.8.8]
lom:
match:
driver: ixgbe
# you are responsible for setting tight enough match rules
# that only match one device if you use set-name
set-name: lom1
dhcp6: true
switchports:
# all cards on second PCI bus; unconfigured by themselves, will be added
# to br0 below (note: globbing is not supported by NetworkManager)
match:
name: enp2*
mtu: 1280
wifis:
all-wlans:
# useful on a system where you know there is only ever going to be one device
match: {}
access-points:
"Joe's home":
# mode defaults to "managed" (client)
password: "s3kr1t"
# this creates an AP on wlp1s0 using hostapd; no match rules, thus ID is
# the interface name
wlp1s0:
access-points:
"guest":
mode: ap
# no WPA config implies default of open
bridges:
# the key name is the name for virtual (created) interfaces; no match: and
# set-name: allowed
br0:
# IDs of the components; switchports expands into multiple interfaces
interfaces: [wlp1s0, switchports]
dhcp4: true
netplan-0.107.1/doc/examples.md 0000664 0000000 0000000 00000040030 14536110317 0016224 0 ustar 00root root 0000000 0000000 # Introduction
Below is a collection of howtos for common scenarios.
If you see a scenario missing or have one to contribute, please file a bug
against this documentation with the example.
To configure Netplan, save configuration files under `/etc/netplan/` with a
`.yaml` extension (e.g. `/etc/netplan/config.yaml`), then run
`sudo netplan apply`. This command parses and applies the configuration to the
system. Configuration written to disk under `/etc/netplan/` will persist between
reboots.
For each of the example below, use the `renderer` that applies to your scenario. For example, for Ubuntu Desktop your `renderer` will probably be `NetworkManager` and `networkd` for Ubuntu Server.
Also, see [/examples](https://github.com/canonical/netplan/tree/main/examples)
on GitHub.
# How to enable DHCP on an interface
To let the interface named `enp3s0` get an address via DHCP, create a YAML file with the following:
```yaml
network:
version: 2
renderer: networkd
ethernets:
enp3s0:
dhcp4: true
```
# How to configure a static IP address on an interface
To set a static IP address, use the `addresses` keyword, which takes a list of (IPv4 or IPv6) addresses along with the subnet prefix length (e.g. /24).
```yaml
network:
version: 2
renderer: networkd
ethernets:
enp3s0:
addresses:
- 10.10.10.2/24
```
# How to configure DNS servers and search domains
The lists of search domains and DNS server IPs can be defined as below:
```yaml
network:
version: 2
renderer: networkd
ethernets:
enp3s0:
addresses:
- 10.10.10.2/24
nameservers:
search:
- "mycompany.local"
addresses:
- 10.10.10.253
- 8.8.8.8
```
# How to connect multiple interfaces with DHCP
DHCP can be used with multiple interfaces. The metrics for the routes acquired from DHCP can be changed with the use of DHCP overrides.
In this example, `enp5s0` is preferred over `enp6s0`, as it has a lower route metric:
```yaml
network:
version: 2
ethernets:
enp5s0:
dhcp4: yes
dhcp4-overrides:
route-metric: 100
enp6s0:
dhcp4: yes
dhcp4-overrides:
route-metric: 200
```
# How to connect to an open wireless network
For open wireless networks, Netplan only requires that the access point is defined. In this example, `opennetwork` is the network SSID:
```yaml
network:
version: 2
wifis:
wl0:
access-points:
opennetwork: {}
dhcp4: yes
```
# How to configure your computer to connect to your home wifi network
If all you need is to connect to your local domestic wifi network, use the configuration below:
```yaml
network:
version: 2
renderer: NetworkManager
wifis:
wlp2s0b1:
dhcp4: yes
access-points:
"network_ssid_name":
password: "**********"
```
# How to connect to a WPA Personal wireless network without DHCP
For private wireless networks, the access point name and password must be specified:
```yaml
network:
version: 2
renderer: networkd
wifis:
wlp2s0b1:
dhcp4: no
dhcp6: no
addresses: [192.168.0.21/24]
nameservers:
addresses: [192.168.0.1, 8.8.8.8]
access-points:
"network_ssid_name":
password: "**********"
routes:
- to: default
via: 192.168.0.1
```
# How to connect to WPA Enterprise wireless networks with EAP+TTLS
```yaml
network:
version: 2
wifis:
wl0:
access-points:
workplace:
auth:
key-management: eap
method: ttls
anonymous-identity: "@internal.example.com"
identity: "joe@internal.example.com"
password: "v3ryS3kr1t"
dhcp4: yes
```
# How to connect to WPA Enterprise wireless networks with EAP+TLS
```yaml
network:
version: 2
wifis:
wl0:
access-points:
university:
auth:
key-management: eap
method: tls
anonymous-identity: "@cust.example.com"
identity: "cert-joe@cust.example.com"
ca-certificate: /etc/ssl/cust-cacrt.pem
client-certificate: /etc/ssl/cust-crt.pem
client-key: /etc/ssl/cust-key.pem
client-key-password: "d3cryptPr1v4t3K3y"
dhcp4: yes
```
Many different modes of encryption are supported. See the [Netplan reference](/reference) page.
# How to use multiple addresses on a single interface
The `addresses` keyword can take a list of addresses to assign to an interface. You can also defined a `label` for each address:
```yaml
network:
version: 2
renderer: networkd
ethernets:
enp3s0:
addresses:
- 10.100.1.37/24
- 10.100.1.38/24:
label: "enp3s0:0"
- 10.100.1.39/24:
label: "enp3s0:some-label"
```
# How to use multiple addresses with multiple gateways
Similar to the example above, interfaces with multiple addresses can be configured with multiple gateways.
```yaml
network:
version: 2
renderer: networkd
ethernets:
enp3s0:
addresses:
- 10.0.0.10/24
- 11.0.0.11/24
routes:
- to: default
via: 10.0.0.1
metric: 200
- to: default
via: 11.0.0.1
metric: 300
```
We configure individual routes to default (or 0.0.0.0/0) using the address of the gateway for the subnet. The `metric` value should be adjusted so the routing happens as expected.
DHCP can be used to receive one of the IP addresses for the interface. In this case, the default route for that address will be automatically configured with a `metric` value of 100.
# How to use Network Manager as a renderer
Netplan supports both networkd and Network Manager as backends. You can specify which network backend should be used to configure particular devices by using the `renderer` key. You can also delegate all configuration of the network to Network Manager itself by specifying only the `renderer` key:
```yaml
network:
version: 2
renderer: NetworkManager
```
# How to configure interface bonding
Bonding is configured by declaring a bond interface with a list of physical interfaces and a bonding mode:
```yaml
network:
version: 2
renderer: networkd
bonds:
bond0:
dhcp4: yes
interfaces:
- enp3s0
- enp4s0
parameters:
mode: active-backup
primary: enp3s0
```
# How to configure multiple bonds
Below is an example of a system acting as a router with various bonded interfaces and different types. Note the 'optional: true' key declarations that allow booting to occur without waiting for those interfaces to activate fully.
```yaml
network:
version: 2
renderer: networkd
ethernets:
enp1s0:
dhcp4: no
enp2s0:
dhcp4: no
enp3s0:
dhcp4: no
optional: true
enp4s0:
dhcp4: no
optional: true
enp5s0:
dhcp4: no
optional: true
enp6s0:
dhcp4: no
optional: true
bonds:
bond-lan:
interfaces: [enp2s0, enp3s0]
addresses: [192.168.93.2/24]
parameters:
mode: 802.3ad
mii-monitor-interval: 1
bond-wan:
interfaces: [enp1s0, enp4s0]
addresses: [192.168.1.252/24]
nameservers:
search: [local]
addresses: [8.8.8.8, 8.8.4.4]
parameters:
mode: active-backup
mii-monitor-interval: 1
gratuitious-arp: 5
routes:
- to: default
via: 192.168.1.1
bond-conntrack:
interfaces: [enp5s0, enp6s0]
addresses: [192.168.254.2/24]
parameters:
mode: balance-rr
mii-monitor-interval: 1
```
# How to configure network bridges
Use the following configuration to create a simple bridge consisting of a single device that uses DHCP:
```yaml
network:
version: 2
renderer: networkd
ethernets:
enp3s0:
dhcp4: no
bridges:
br0:
dhcp4: yes
interfaces:
- enp3s0
```
# How to create a bridge with a VLAN for libvirtd
To get libvirtd to use a specific bridge with a tagged vlan, while continuing to provide an untagged interface as well would involve:
```yaml
network:
version: 2
renderer: networkd
ethernets:
enp0s25:
dhcp4: true
bridges:
br0:
addresses: [ 10.3.99.25/24 ]
interfaces: [ vlan15 ]
vlans:
vlan15:
accept-ra: no
id: 15
link: enp0s25
```
Then libvirtd would be configured to use this bridge by adding the following content to a new XML file under `/etc/libvirtd/qemu/networks/`. The name of the bridge in the <bridge> tag as well as in <name> need to match the name of the bridge device configured using netplan:
```xml
br0
```
# How to create VLANs
To configure multiple VLANs with renamed interfaces:
```yaml
network:
version: 2
renderer: networkd
ethernets:
mainif:
match:
macaddress: "de:ad:be:ef:ca:fe"
set-name: mainif
addresses: [ "10.3.0.5/23" ]
nameservers:
addresses: [ "8.8.8.8", "8.8.4.4" ]
search: [ example.com ]
routes:
- to: default
via: 10.3.0.1
vlans:
vlan15:
id: 15
link: mainif
addresses: [ "10.3.99.5/24" ]
vlan10:
id: 10
link: mainif
addresses: [ "10.3.98.5/24" ]
nameservers:
addresses: [ "127.0.0.1" ]
search: [ domain1.example.com, domain2.example.com ]
```
# How to use a directly connected gateway
This allows setting up a default route, or any route, using the "on-link" keyword where the gateway is an IP address that is directly connected to the network even if the address does not match the subnet configured on the interface.
```yaml
network:
version: 2
renderer: networkd
ethernets:
ens3:
addresses: [ "10.10.10.1/24" ]
routes:
- to: default # or 0.0.0.0/0
via: 9.9.9.9
on-link: true
```
For IPv6 the config would be very similar:
```yaml
network:
version: 2
renderer: networkd
ethernets:
ens3:
addresses: [ "2001:cafe:face:beef::dead:dead/64" ]
routes:
- to: default # or "::/0"
via: "2001:cafe:face::1"
on-link: true
```
# How to configure source routing
In the example below, ens3 is on the 192.168.3.0/24 network and ens5 is on the 192.168.5.0/24 network. This enables clients on either network to connect to the other and allow the response to come from the correct interface.
Furthermore, the default route is still assigned to ens5 allowing any other traffic to go through it.
```yaml
network:
version: 2
renderer: networkd
ethernets:
ens3:
addresses:
- 192.168.3.30/24
dhcp4: no
routes:
- to: 192.168.3.0/24
via: 192.168.3.1
table: 101
routing-policy:
- from: 192.168.3.0/24
table: 101
ens5:
addresses:
- 192.168.5.24/24
dhcp4: no
routes:
- to: default
via: 192.168.5.1
- to: 192.168.5.0/24
via: 192.168.5.1
table: 102
routing-policy:
- from: 192.168.5.0/24
table: 102
```
# How to configure a loopback interface
Networkd does not allow creating new loopback devices, but a user can add new addresses to the standard loopback interface, lo, in order to have it considered a valid address on the machine as well as for custom routing:
```yaml
network:
version: 2
renderer: networkd
ethernets:
lo:
addresses: [ "127.0.0.1/8", "::1/128", "7.7.7.7/32" ]
```
# How to integrate with Windows DHCP Server
For networks where DHCP is provided by a Windows Server using the dhcp-identifier keyword allows for interoperability:
```yaml
network:
version: 2
ethernets:
enp3s0:
dhcp4: yes
dhcp-identifier: mac
```
# How to connect to an IPv6 over IPv4 tunnel
Here, 1.1.1.1 is the client's own IP address; 2.2.2.2 is the remote server's IPv4 address, "2001:dead:beef::2/64" is the client's IPv6 address as defined by the tunnel, and "2001:dead:beef::1" is the remote server's IPv6 address.
Finally, "2001:cafe:face::1/64" is an address for the client within the routed IPv6 prefix:
```yaml
network:
version: 2
ethernets:
eth0:
addresses:
- 1.1.1.1/24
- "2001:cafe:face::1/64"
routes:
- to: default
via: 1.1.1.254
tunnels:
he-ipv6:
mode: sit
remote: 2.2.2.2
local: 1.1.1.1
addresses:
- "2001:dead:beef::2/64"
routes:
- to: default
via: "2001:dead:beef::1"
```
# How to configure SR-IOV Virtual Functions
For SR-IOV network cards, it is possible to dynamically allocate Virtual Function interfaces for every configured Physical Function. In netplan, a VF is defined by having a link: property pointing to the parent PF.
```yaml
network:
version: 2
ethernets:
eno1:
mtu: 9000
enp1s16f1:
link: eno1
addresses : [ "10.15.98.25/24" ]
vf1:
match:
name: enp1s16f[2-3]
link: eno1
addresses : [ "10.15.99.25/24" ]
```
# How to connect two systems with a WireGuard VPN
Generate the private and public keys in the first peer:
```bash
# wg genkey > private.key
# wg pubkey < private.key > public.key
# cat private.key
UMjI9WbobURkCDh2RT8SRM5osFI7siiR/sPOuuTIDns=
# cat public.key
EdNnZ1/2OJZ9HcScSVcwDVUsctCkKQ/xzjEyd3lZFFs=
```
Do the same in the second peer:
```bash
# wg genkey > private.key
# wg pubkey < private.key > public.key
# cat private.key
UAmjvLDVuV384OWFJkmI4bG8AIAZAfV7LarshnV3+lc=
# cat public.key
AIm+QeCoC23zInKASmhu6z/3iaT0R2IKraB7WwYB5ms=
```
Use the following configuration in the `first peer` (replace the keys and IPs as needed):
```yaml
network:
tunnels:
wg0:
mode: wireguard
port: 51820
key: UMjI9WbobURkCDh2RT8SRM5osFI7siiR/sPOuuTIDns=
addresses:
- 172.16.0.1/24
peers:
- allowed-ips: [172.16.0.0/24]
endpoint: 10.86.126.56:51820
keys:
public: AIm+QeCoC23zInKASmhu6z/3iaT0R2IKraB7WwYB5ms=
```
In the YAML file above, `key` is the first peer's `private key` and
`public` is the second peer's `public key`. `endpoint` is the `second peer` IP address.
Use the following configuration in the `second peer`:
```yaml
network:
tunnels:
wg0:
mode: wireguard
port: 51820
key: UAmjvLDVuV384OWFJkmI4bG8AIAZAfV7LarshnV3+lc=
addresses:
- 172.16.0.2/24
peers:
- allowed-ips: [172.16.0.0/24]
endpoint: 10.86.126.40:51820
keys:
public: EdNnZ1/2OJZ9HcScSVcwDVUsctCkKQ/xzjEyd3lZFFs=
```
In the YAML file above, `key` is the second peer's `private key` and
`public` is the first peer's `public key`. `endpoint` is the `first peer's` IP address.
# How to connect your home computer to a cloud instance with a WireGuard VPN
Follow the same steps from the previous howto to generate the necessary keys.
The difference here is that your computer is likely behind one or more devices doing NAT so you probably don't have a static public IP to use as endpoint in the remote system.
Use the following configuration in your computer:
```yaml
network:
tunnels:
wg0:
mode: wireguard
port: 51821
key: UMjI9WbobURkCDh2RT8SRM5osFI7siiR/sPOuuTIDns=
addresses:
- 172.17.0.1/24
peers:
- allowed-ips: [172.17.0.0/24]
endpoint: 54.234.x.y:51821
keys:
public: AIm+QeCoC23zInKASmhu6z/3iaT0R2IKraB7WwYB5ms=
```
Again, `key` is your private key and `public` is the remote system's public key. The `endpoint` is the public IP address of your instance.
In the remote instance you just need to omit the `endpoint`.
```yaml
network:
tunnels:
wg0:
mode: wireguard
port: 51821
key: UAmjvLDVuV384OWFJkmI4bG8AIAZAfV7LarshnV3+lc=
addresses:
- 172.17.0.2/24
peers:
- allowed-ips: [172.17.0.0/24]
keys:
public: EdNnZ1/2OJZ9HcScSVcwDVUsctCkKQ/xzjEyd3lZFFs=
```
Don't forget to allow the UDP port `51821` in your instance's security group.
After applying your configuration you should be able to reach your remote instance through the IP address `172.17.0.2`.
netplan-0.107.1/doc/explanation.md 0000664 0000000 0000000 00000000523 14536110317 0016733 0 ustar 00root root 0000000 0000000 # Explanation
## General structure & IDs
```{toctree}
structure-id
```
## NetworkManager
```{toctree}
nm-all
```
## Design
Network configuration abstraction via systemd-generator
```{toctree}
Netplan Design
```
## FAQs
Find answers to common questions
```{toctree}
Netplan FAQs
```
netplan-0.107.1/doc/howto.md 0000664 0000000 0000000 00000000132 14536110317 0015545 0 ustar 00root root 0000000 0000000 # How-to guides
```{toctree}
examples
dbus-config
netplan-everywhere
contribute-docs
```
netplan-0.107.1/doc/index.md 0000664 0000000 0000000 00000004317 14536110317 0015525 0 ustar 00root root 0000000 0000000 # Netplan documentation
```{toctree}
---
maxdepth: 2
hidden: true
---
tutorial
howto
reference
explanation
```
**Netplan** is a network configuration abstraction renderer.
It is a **utility for network configuration** on a Linux system. You create a
description of the required interfaces and define what each should do.
Netplan meets the need of **easy, descriptive network configuration** in YAML
across a versatile set of server, desktop, cloud or IoT installations.
It is useful for **administrators of a Linux system** who want to use a common
network configuration, controlling different backends like NetworkManager or
systemd-networkd.
## In this documentation
::::{grid} 1 1 2 2
:::{grid-item-card} **[Tutorial](/netplan-tutorial)**
:link: /netplan-tutorial
:link-type: doc
**Get started** - hands-on introduction to Netplan for new users
:::
:::{grid-item-card} **[How-to guides](/examples)**
:link: /examples
:link-type: doc
**Step-by-step guides** covering key operations and common tasks
:::
::::
::::{grid} 1 1 2 2
:reverse:
:::{grid-item-card} **[Reference](/reference)**
:link: /reference
:link-type: doc
**Technical information** - specifications, APIs, architecture
:::
:::{grid-item-card} **[Explanation](/explanation)**
:link: /explanation
:link-type: doc
**Discussion and clarification** of key topics
:::
::::
## Project and community
Netplan is a member of the Ubuntu family. It’s an open source project that
warmly welcomes community contributions, suggestions, fixes and constructive
feedback.
* **[Read our code of conduct](https://ubuntu.com/community/code-of-conduct)**:
As a community we adhere to the Ubuntu code of conduct.
* **[Get support](https://askubuntu.com/questions/tagged/netplan)**:
Ask Ubuntu is a question and answer site for Ubuntu users and developers.
* **[Join our online chat](https://web.libera.chat/gamja/?channels=%23netplan)**:
Meet us in #netplan on IRC Libera.Chat.
* **[Report bugs](https://bugs.launchpad.net/netplan/+filebug)**:
We want to know about the problems so we can fix them.
* **[Contribute code](https://github.com/canonical/netplan)**:
The code is open and we are open to accepting changes to it.
Thinking about using Netplan? [Get in touch!](https://netplan.io)
netplan-0.107.1/doc/manpage-footer.md 0000664 0000000 0000000 00000000423 14536110317 0017314 0 ustar 00root root 0000000 0000000 # SEE ALSO
**netplan-generate**(8), **netplan-apply**(8), **netplan-try**(8), **netplan-get**(8), **netplan-set**(8), **netplan-info**(8), **netplan-ip**(8), **netplan-rebind**(8), **netplan-status**(8), **netplan-dbus**(8), **systemd-networkd**(8), **NetworkManager**(8)
netplan-0.107.1/doc/manpage-header.md 0000664 0000000 0000000 00000000613 14536110317 0017247 0 ustar 00root root 0000000 0000000 ---
title: netplan
section: 5
author:
- Mathieu Trudel-Lapierre ()
- Martin Pitt ()
- Lukas Märdian ()
...
# NAME
netplan - YAML network configuration abstraction for various backends
# SYNOPSIS
**netplan** [ *COMMAND* | help ]
# COMMANDS
See **netplan help** for a list of available commands on this system.
# DESCRIPTION
netplan-0.107.1/doc/meson.build 0000664 0000000 0000000 00000002436 14536110317 0016236 0 ustar 00root root 0000000 0000000 if pandoc.found()
custom_target(
input: ['manpage-header.md', 'structure-id.md', 'netplan-yaml.md', 'manpage-footer.md'],
output: 'netplan.5',
command: [pandoc, '-s', '-o', '@OUTPUT@', '--from=markdown-smart', '@INPUT@'],
install: true,
install_dir: join_paths(get_option('mandir'), 'man5'))
custom_target(
input: 'netplan-yaml.md',
output: 'netplan.html',
command: [pandoc, '-s', '--metadata', 'title="Netplan reference"', '--toc', '-o', '@OUTPUT@', '@INPUT@'],
install: true,
install_dir: join_paths(get_option('datadir'), 'doc', 'netplan'))
foreach doc : [
'netplan-apply', 'netplan-dbus', 'netplan-generate', 'netplan-get', 'netplan-set',
'netplan-try', 'netplan-info', 'netplan-ip', 'netplan-status', 'netplan-rebind',
]
markdown = files(doc + '.md')
manpage = doc + '.8'
custom_target(
input: markdown,
output: manpage,
command: [pandoc, '-s', '-o', '@OUTPUT@', '--shift-heading-level-by=-1', '--from=markdown-smart', '@INPUT@'],
install: true,
install_dir: join_paths(get_option('mandir'), 'man8'))
endforeach
else
warning('Program "pandoc" not found! Cannot generate documentation/man pages')
endif
netplan-0.107.1/doc/netplan-apply.md 0000664 0000000 0000000 00000004047 14536110317 0017202 0 ustar 00root root 0000000 0000000 ---
title: netplan-apply
section: 8
author:
- Daniel Axtens ()
...
## NAME
netplan-apply - apply configuration from netplan YAML files to a running system
## SYNOPSIS
**netplan** [--debug] **apply** -h | --help
**netplan** [--debug] **apply**
## DESCRIPTION
**netplan apply** applies the current netplan configuration to a running system.
The process works as follows:
1. The backend configuration is generated from netplan YAML files.
2. The appropriate backends (**systemd-networkd**(8) or
**NetworkManager**(8)) are invoked to bring up configured interfaces.
3. **netplan apply** iterates through interfaces that are still down, unbinding
them from their drivers, and rebinding them. This gives **udev**(7) renaming
rules the opportunity to run.
4. If any devices have been rebound, the appropriate backends are re-invoked in
case more matches can be done.
For information about the generation step, see
**netplan-generate**(8). For details of the configuration file format,
see **netplan**(5).
## OPTIONS
-h, --help
: Print basic help.
--debug
: Print debugging output during the process.
## KNOWN ISSUES
**netplan apply** will not remove virtual devices such as bridges and bonds
that have been created, even if they are no longer described in the netplan
configuration. That is due to the fact that netplan operates statelessly and
is not aware of the previously defined virtual devices.
This can be resolved by manually removing the virtual device (for example
``ip link delete dev bond0``) and then running **netplan apply**, by rebooting,
or by creating a temporary backup of the YAML state in ``/etc/netplan``
before modifying the configuration and passing this state to netplan (e.g.
``mkdir -p /tmp/netplan_state_backup/etc && cp -r /etc/netplan /tmp/netplan_state_backup/etc/``
then running **netplan apply --state /tmp/netplan_state_backup**)
## SEE ALSO
**netplan**(5), **netplan-generate**(8), **netplan-try**(8), **udev**(7),
**systemd-networkd.service**(8), **NetworkManager**(8)
netplan-0.107.1/doc/netplan-dbus.md 0000664 0000000 0000000 00000005212 14536110317 0017005 0 ustar 00root root 0000000 0000000 ---
title: netplan-dbus
section: 8
author:
- Lukas Märdian ()
...
## NAME
netplan-dbus - daemon to access netplan's functionality via a DBus API
## SYNOPSIS
**netplan-dbus**
## DESCRIPTION
**netplan-dbus** is a DBus daemon, providing ``io.netplan.Netplan`` on the system bus. The ``/io/netplan/Netplan`` object provides an ``io.netplan.Netplan`` interface, offering the following methods:
* ``Apply() -> b``: calls **netplan apply** and returns a success or failure status.
* ``Generate() -> b``: calls **netplan generate** and returns a success or failure status.
* ``Info() -> a(sv)``: returns a dict "Features -> as", containing an array of all available feature flags.
* ``Config() -> o``: prepares a new config object as ``/io/netplan/Netplan/config/``, by copying the current state from ``/{etc,run,lib}/netplan/*.yaml``
The ``/io/netplan/Netplan/config/`` objects provide a ``io.netplan.Netplan.Config`` interface, offering the following methods:
* ``Get() -> s``: calls **netplan get --root-dir=/run/netplan/config-ID all** and returns the merged YAML config of the the given config object's state
* ``Set(s:CONFIG_DELTA, s:ORIGIN_HINT) -> b``: calls **netplan set --root-dir=/run/netplan/config-ID --origin-hint=ORIGIN_HINT CONFIG_DELTA**
CONFIG_DELTA can be something like: ``network.ethernets.eth0.dhcp4=true`` and
ORIGIN_HINT can be something like: ``70-snapd`` (it will then write the config
to ``70-snapd.yaml``). Once ``Set()`` is called on a config object, all other
current and future config objects are being invalidated and cannot ``Set()`` or
``Try()/Apply()`` anymore, due to this pending dirty state. After the dirty
config object is rejected via ``Cancel()``, the other config objects are valid
again. If the dirty config object is accepted via ``Apply()``, newly created
config objects will be valid, while the older states will stay invalid.
* ``Try(u:TIMEOUT_SEC) -> b``: replaces the main netplan configuration with this config object's state and calls **netplan try --timeout=TIMEOUT_SEC**
* ``Cancel() -> b``: rejects a currently running ``Try()`` attempt on this config object and/or discards the config object
* ``Apply() -> b``: replaces the main netplan configuration with this config object's state and calls **netplan apply**
For information about the Apply()/Try()/Get()/Set() functionality, see
**netplan-apply**(8)/**netplan-try**(8)/**netplan-get**(8)/**netplan-set**(8)
accordingly. For details of the configuration file format, see **netplan**(5).
## SEE ALSO
**netplan**(5), **netplan-apply**(8), **netplan-try**(8), **netplan-get**(8),
**netplan-set**(8)
netplan-0.107.1/doc/netplan-everywhere.md 0000664 0000000 0000000 00000012605 14536110317 0020241 0 ustar 00root root 0000000 0000000 # Desktop integration
## NetworkManager YAML settings backend
NetworkManager is the tool used by Ubuntu Desktop systems to manage
network devices such as Ethernet and Wifi adapters. While it is a great
tool for the job and users can directly use it through the command line
and the graphical interfaces to configure their devices, Ubuntu has its
own way of describing and storing network configuration via Netplan.
On Ubuntu 23.10 "Mantic Minotaur" and later, NetworkManager uses Netplan APIs
to save the configuration created using any of its graphical or programmatic
interfaces. This leads to having a centralized location to store network
configuration. On the Desktop, it's convenient to use graphical tools for
configuration when they are available, so nothing changes from the user
perspective; only the way the system handles the configuration in the background.
For more information on Netplan, see [https://netplan.io](https://netplan.io).
For more information on NetworkManager, see [https://networkmanager.dev](https://networkmanager.dev).
## How it works
Every time a non-temporary connection is created in NetworkManager, instead
of persisting the original `.nmconnection` file, it creates a Netplan YAML
file in `/etc/netplan/` called `90-NM-.yaml`. After creating
the file, NetworkManager calls the Netplan generator to provide the
configuration for that connection. Connections that are temporary, like the ones
created for virtual network interfaces when you connect to a VPN for example,
are not persisted as Netplan files. The reason for that is that these interfaces
are usually managed by external services and we don't want to cause any
unexpected change that would affect them.
## How to use
### Installing NetworkManager
The NetworkManager 1.44.2 package containing the Netplan integration patch
is available by default in Ubuntu 23.10 "Mantic Minotaur" and later as part of
the official Ubuntu archive.
```
$ sudo apt update
$ sudo apt install network-manager
```
### User interface
From this point on, Netplan is aware of all your network configuration and
you can query it using its CLI tools, such as `sudo netplan get` or `sudo
netplan status`. All while keeping untouched the traditional way of modifying
it using NetworkManager (graphical UI, GNOME Quick Settings, `nmcli`,
`nmtui`, D-Bus APIs, ...).
### Management of connection profiles
The NetworkManager-Netplan integration imports connection profiles from
`/etc/NetworkManager/system-connections/` to Netplan during the installation
process. It automatically creates a copy of all your connection profiles during
the installation of the new network-manager package in
`/root/NetworkManager.bak/system-connections/`. The same migration happens
in the background whenever you add or modify any connection profile.
You can observe this migration on the `apt-get`` command line. Watch for
logs like the following:
```
Setting up network-manager (1.44.2-1ubuntu1.2) ...
Migrating HomeNet (9d087126-ae71-4992-9e0a-18c5ea92a4ed) to /etc/netplan
Migrating eduroam (37d643bb-d81d-4186-9402-7b47632c59b1) to /etc/netplan
Migrating DebConf (f862be9c-fb06-4c0f-862f-c8e210ca4941) to /etc/netplan
```
For example, if you have a Wifi connection, you will not find the connection
profile file at `/etc/NetworkManager/system-connections/` anymore. Instead,
the system removes the profile file, and Netplan creates a new YAML file called
`90-NM-.yaml` in `/etc/netplan/` and generates a new ephemeral
profile in `/run/NetworkManager/system-connections/`.
## Limitation
Netplan doesn't yet support all the configuration options available in
NetworkManager (or doesn't know how to interpret some of the keywords
found in the keyfile). After creating a new connection you might find
a section called `passthrough` in your YAML file, like in the example below:
```yaml
network:
version: 2
ethernets:
NM-0f7a33ac-512e-4c03-b088-4db00fe3292e:
renderer: NetworkManager
match:
name: "enp1s0"
nameservers:
addresses:
- 8.8.8.8
dhcp4: true
wakeonlan: true
networkmanager:
uuid: "0f7a33ac-512e-4c03-b088-4db00fe3292e"
name: "Ethernet connection 1"
passthrough:
ethernet._: ""
ipv4.ignore-auto-dns: "true"
ipv6.addr-gen-mode: "default"
ipv6.method: "disabled"
ipv6.ip6-privacy: "-1"
proxy._: ""
```
All the configuration under the `passthrough` mapping is added to
the `.nmconnection` file as they are.
In cases where the connection type is not supported by Netplan, the system uses
the `nm-devices` network type. The example below is an OpenVPN client
connection, which is not supported by Netplan at the moment.
```yaml
network:
version: 2
nm-devices:
NM-db5f0f67-1f4c-4d59-8ab8-3d278389cf87:
renderer: NetworkManager
networkmanager:
uuid: "db5f0f67-1f4c-4d59-8ab8-3d278389cf87"
name: "myvpnconnection"
passthrough:
connection.type: "vpn"
vpn.ca: "path to ca.crt"
vpn.cert: "path to client.crt"
vpn.cipher: "AES-256-GCM"
vpn.connection-type: "tls"
vpn.dev: "tun"
vpn.key: "path to client.key"
vpn.remote: "1.2.3.4:1194"
vpn.service-type: "org.freedesktop.NetworkManager.openvpn"
ipv4.method: "auto"
ipv6.addr-gen-mode: "default"
ipv6.method: "auto"
proxy._: ""
```
netplan-0.107.1/doc/netplan-generate.md 0000664 0000000 0000000 00000005201 14536110317 0017640 0 ustar 00root root 0000000 0000000 ---
title: netplan-generate
section: 8
author:
- Daniel Axtens ()
...
## NAME
netplan-generate - generate backend configuration from netplan YAML files
## SYNOPSIS
**netplan** [--debug] **generate** -h | --help
**netplan** [--debug] **generate** [--root-dir _ROOT_DIR_] [--mapping _MAPPING_]
## DESCRIPTION
netplan generate converts netplan YAML into configuration files
understood by the backends (**systemd-networkd**(8) or
**NetworkManager**(8)). It *does not* apply the generated
configuration.
You will not normally need to run this directly as it is run by
**netplan apply**, **netplan try**, or at boot.
Only if executed during the systemd ``initializing`` phase
(i.e. "Early bootup, before ``basic.target`` is reached"), will
it attempt to start/apply the newly created service units.
**Requires feature: generate-just-in-time**
For details of the configuration file format, see **netplan**(5).
## OPTIONS
-h, --help
: Print basic help.
--debug
: Print debugging output during the process.
--root-dir _ROOT_DIR_
: Instead of looking in /{lib,etc,run}/netplan, look in
/_ROOT_DIR_/{lib,etc,run}/netplan
--mapping _MAPPING_
: Instead of generating output files, parse the configuration files
and print some internal information about the device specified in
_MAPPING_.
## HANDLING MULTIPLE FILES
There are 3 locations that netplan generate considers:
* /lib/netplan/*.yaml
* /etc/netplan/*.yaml
* /run/netplan/*.yaml
If there are multiple files with exactly the same name, then only one
will be read. A file in /run/netplan will shadow - completely replace
- a file with the same name in /etc/netplan. A file in /etc/netplan
will itself shadow a file in /lib/netplan.
Or in other words, /run/netplan is top priority, then /etc/netplan,
with /lib/netplan having the lowest priority.
If there are files with different names, then they are considered in
lexicographical order - regardless of the directory they are in. Later
files add to or override earlier files. For example,
/run/netplan/10-foo.yaml would be updated by /lib/netplan/20-abc.yaml.
If you have two files with the same key/setting, the following rules
apply:
* If the values are YAML boolean or scalar values (numbers and
strings) the old value is overwritten by the new value.
* If the values are sequences, the sequences are concatenated - the
new values are appended to the old list.
* If the values are mappings, netplan will examine the elements
of the mappings in turn using these rules.
## SEE ALSO
**netplan**(5), **netplan-apply**(8), **netplan-try**(8),
**systemd-networkd**(8), **NetworkManager**(8)
netplan-0.107.1/doc/netplan-get.md 0000664 0000000 0000000 00000001602 14536110317 0016626 0 ustar 00root root 0000000 0000000 ---
title: netplan-get
section: 8
author:
- Lukas Märdian (lukas.maerdian@canonical.com)
...
## NAME
netplan-get - read merged netplan YAML configuration
## SYNOPSIS
**netplan** [--debug] **get** -h | --help
**netplan** [--debug] **get** [--root-dir=ROOT_DIR] [key]
## DESCRIPTION
**netplan get [key]** reads all YAML files from ``/{etc,lib,run}/netplan/*.yaml`` and returns a merged view of the current configuration
You can specify ``all`` as a key (the default) to get the full YAML tree or extract a subtree by specifying a nested key like: ``[network.]ethernets.eth0``.
For details of the configuration file format, see **netplan**(5).
## OPTIONS
-h, --help
: Print basic help.
--debug
: Print debugging output during the process.
--root-dir
: Read YAML files from this root instead of /
## SEE ALSO
**netplan**(5), **netplan-set**(8), **netplan-dbus**(8)
netplan-0.107.1/doc/netplan-info.md 0000664 0000000 0000000 00000001125 14536110317 0017002 0 ustar 00root root 0000000 0000000 ---
title: netplan-info
section: 8
author:
- Danilo Egea Gondolfo (danilo.egea.gondolfo@canonical.com)
...
## NAME
netplan-info - show available features
## SYNOPSIS
**netplan** [--debug] **info** -h | --help
**netplan** [--debug] **info** [--json | --yaml]
## DESCRIPTION
**netplan info** displays the supported features.
## OPTIONS
-h, --help
: Print basic help.
--debug
: Print debugging output during the process.
--json
: Output version and features in JSON format.
--yaml
: Output version and features in YAML format (default).
## SEE ALSO
**netplan**(5)
netplan-0.107.1/doc/netplan-ip.md 0000664 0000000 0000000 00000001361 14536110317 0016461 0 ustar 00root root 0000000 0000000 ---
title: netplan-ip
section: 8
author:
- Danilo Egea Gondolfo (danilo.egea.gondolfo@canonical.com)
...
## NAME
netplan-ip - retrieve IP information (like DHCP leases) from the system
## SYNOPSIS
**netplan** [--debug] **ip** -h | --help
**netplan** [--debug] **ip** COMMAND [--root-dir=ROOT_DIR] ARGUMENTS
## DESCRIPTION
**netplan ip** retrieves IP information (like DHCP leases) from the system.
## DHCP COMMANDS
**leases** `INTERFACE`: Displays DHCP IP leases
Example: netplan ip leases enp5s0
## OPTIONS
-h, --help
: Print basic help.
--debug
: Print debugging output during the process.
--root-dir
: Read YAML files from this root instead of /
## SEE ALSO
**netplan**(5), **netplan-get**(8), **netplan-status**(8)
netplan-0.107.1/doc/netplan-rebind.md 0000664 0000000 0000000 00000001226 14536110317 0017314 0 ustar 00root root 0000000 0000000 ---
title: netplan-rebind
section: 8
author:
- Danilo Egea Gondolfo (danilo.egea.gondolfo@canonical.com)
...
## NAME
netplan-rebind - rebind SR-IOV virtual functions to their driver
## SYNOPSIS
**netplan** [--debug] **rebind** -h | --help
**netplan** [--debug] **rebind** [interfaces]
## DESCRIPTION
**netplan rebind [interfaces]** rebinds SR-IOV virtual functions of given physical functions to their driver.
## OPTIONS
-h, --help
: Print basic help.
--debug
: Print debugging output during the process.
interfaces
: Space separated list of PF interface names.
## SEE ALSO
**netplan**(5), **netplan-set**(8), **netplan-apply**(8)
netplan-0.107.1/doc/netplan-set.md 0000664 0000000 0000000 00000002105 14536110317 0016641 0 ustar 00root root 0000000 0000000 ---
title: netplan-set
section: 8
author:
- Lukas Märdian (lukas.maerdian@canonical.com)
...
## NAME
netplan-set - write netplan YAML configuration snippets to file
## SYNOPSIS
**netplan** [--debug] **set** -h | --help
**netplan** [--debug] **set** [--root-dir=ROOT_DIR] [--origin-hint=ORIGIN_HINT] [key=value]
## DESCRIPTION
**netplan set [key=value]** writes a given key/value pair or YAML subtree into a YAML file in ``/etc/netplan/`` and validates its format.
You can specify a single value as: ``"[network.]ethernets.eth0.addresses=[1.2.3.4/24, 5.6.7.8/24]"`` or a full subtree as: ``"[network.]ethernets.eth0={dhcp4: true, dhcp6: true}"``.
For details of the configuration file format, see **netplan**(5).
## OPTIONS
-h, --help
: Print basic help.
--debug
: Print debugging output during the process.
--root-dir
: Write YAML files into this root instead of /
--origin-hint
: Specify a name for the config file, e.g.: ``70-netplan-set`` => ``/etc/netplan/70-netplan-set.yaml``
## SEE ALSO
**netplan**(5), **netplan-get**(8), **netplan-dbus**(8)
netplan-0.107.1/doc/netplan-status.md 0000664 0000000 0000000 00000001663 14536110317 0017401 0 ustar 00root root 0000000 0000000 ---
title: netplan-status
section: 8
author:
- Danilo Egea Gondolfo (danilo.egea.gondolfo@canonical.com)
...
## NAME
netplan-status - query networking state of the running system
## SYNOPSIS
**netplan** [--debug] **status** -h | --help
**netplan** [--debug] **status** [interface]
## DESCRIPTION
**netplan status [interface]** queries the current network configuration and displays it in human readable format.
You can specify ``interface`` to display the status of a specific interface.
Currently, **netplan status** depends on `systemd-networkd` as a source of data and will try to start it if it's not masked.
## OPTIONS
-h, --help
: Print basic help.
--debug
: Print debugging output during the process.
-a, --all
: Show all interface data including inactive
-f FORMAT, --format FORMAT
: Output in machine readable `json` or `yaml` format
## SEE ALSO
**netplan**(5), **netplan-get**(8), **netplan-ip**(8)
netplan-0.107.1/doc/netplan-try.md 0000664 0000000 0000000 00000003314 14536110317 0016667 0 ustar 00root root 0000000 0000000 ---
title: netplan-try
section: 8
author:
- Daniel Axtens ()
...
## NAME
netplan-try - try a configuration, optionally rolling it back
## SYNOPSIS
**netplan** [--debug] **try** -h | --help
**netplan** [--debug] **try** [--config-file _CONFIG_FILE_] [--timeout _TIMEOUT_]
## DESCRIPTION
**netplan try** takes a **netplan**(5) configuration, applies it, and
automatically rolls it back if the user does not confirm the
configuration within a time limit.
A configuration can be confirmed or rejected interactively or by sending the
SIGUSR1 or SIGINT signals.
This may be especially useful on remote systems, to prevent an
administrator being permanently locked out of systems in the case of a
network configuration error.
## OPTIONS
-h, --help
: Print basic help.
--debug
: Print debugging output during the process.
--config-file _CONFIG_FILE_
: In addition to the usual configuration, apply _CONFIG_FILE_. It must
be a YAML file in the **netplan**(5) format.
--timeout _TIMEOUT_
: Wait for _TIMEOUT_ seconds before reverting. Defaults to 120
seconds. Note that some network configurations (such as STP) may take
over a minute to settle.
## KNOWN ISSUES
**netplan try** uses similar procedures to **netplan apply**, so some
of the same caveats apply around virtual devices.
There are also some known bugs: if **netplan try** times out or is
cancelled, make sure to verify if the network configuration has in
fact been reverted.
As with **netplan apply**, a reboot should fix any issues. However, be
sure to verify that the config on disk is in the state you expect
before rebooting!
## SEE ALSO
**netplan**(5), **netplan-generate**(8), **netplan-apply**(8)
netplan-0.107.1/doc/netplan-tutorial.md 0000664 0000000 0000000 00000050563 14536110317 0017724 0 ustar 00root root 0000000 0000000 # Pre-requisites
In order to do the exercises yourself you will need a virtual machine, preferably running Ubuntu. In this tutorial, we will use LXD to create virtual networks and launch virtual machines. Feel free to use a cloud instance or a different hypervisor. As long as you can achieve the same results, you should be fine. If you're going to use your own desktop/laptop system, some of the exercises might interrupt your network connectivity.
If you already have a setup where you can do the exercises you can just skip this section.
## Setting up the environment
You can follow the steps below to install and create a basic LXD configuration you can use to launch virtual machines.
For more information about LXD, please visit [linuxcontainers.org](https://linuxcontainers.org/lxd/introduction/).
First, install LXD: [LXD | How to install LXD](https://linuxcontainers.org/lxd/docs/latest/installing/)
On Ubuntu, you can install it using `snap`:
```
$ snap install lxd
```
Now, initialize your LXD configuration:
```
$ lxd init --minimal
```
Run the command below to create a new network in LXD. For some of the exercises you will need a second network interface in your virtual machine.
```
lxc network create netplanbr0 --type=bridge
```
You should see the output below:
```
Network netplanbr0 created
```
At this point you should have a usable LXD installation with a working network bridge.
Now create a virtual machine called `netplan-lab0`:
```
lxc init --vm ubuntu:22.04 netplan-lab0
```
You should see the output below:
```
Creating netplan-lab0
```
The new VM will have one network interface attached to the default LXD bridge.
Now attach the network you created just now, `netplanbr0`, to your VM, `netplan-lab0`, as interface `eth1`:
```
lxc network attach netplanbr0 netplan-lab0 eth1
```
> See more: [LXD | Attach a network to an instance](https://linuxcontainers.org/lxd/docs/latest/howto/network_create/#attach-a-network-to-an-instance)
And start your new VM:
```
lxc start netplan-lab0
```
Access your new VM using `lxc shell`:
```
lxc shell netplan-lab0
```
> **If this doesn't work for you**: Try instead `lxc exec netplan-lab0 bash` or `lxc console netplan-lab0`.
You should now have a root shell inside your VM:
```
root@netplan-lab0:~#
```
Run the command `ip link` to show your network interfaces:
```
ip link
```
You should see an output similar to the below:
```
1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp5s0: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:16:3e:13:ae:10 brd ff:ff:ff:ff:ff:ff
3: enp6s0: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 00:16:3e:0c:97:8a brd ff:ff:ff:ff:ff:ff
```
In this case, `enp5s0` is the primary interface connected to the default LXD network and `enp6s0` is the second interface you added connected to your custom network.
Now, let's start with a simple exercise.
# Running netplan for the first time
Start by typing the command `netplan` in your shell:
```
netplan
```
You should see the output below
```
You need to specify a command
usage: /usr/sbin/netplan [-h] [--debug] ...
Network configuration in YAML
options:
-h, --help show this help message and exit
--debug Enable debug messages
Available commands:
help Show this help message
apply Apply current netplan config to running system
generate Generate backend specific configuration files from /etc/netplan/*.yaml
get Get a setting by specifying a nested key like "ethernets.eth0.addresses", or "all"
info Show available features
ip Retrieve IP information from the system
set Add new setting by specifying a dotted key=value pair like ethernets.eth0.dhcp4=true
rebind Rebind SR-IOV virtual functions of given physical functions to their driver
status Query networking state of the running system
try Try to apply a new netplan config to running system, with automatic rollback
```
As you can see, netplan has a number of sub commands. Let's explore some of them.
# Showing your current netplan configuration
To show your current configuration, run the command `netplan get`.
```
netplan get
```
You should see an output similar to the one below:
```yaml
network:
version: 2
ethernets:
enp5s0:
dhcp4: true
```
It shows you have an ethernet interface called `enp5s0` and it has DHCP enabled for IPv4.
# Showing your current network configuration
Netplan 0.106 introduced the `netplan status` command. You can use it to
show your system's current network configuration. Try that by typing
`netplan status --all` in your console:
```
netplan status --all
```
You should see an output similar to the one below:
```
Online state: online
DNS Addresses: 127.0.0.53 (stub)
DNS Search: lxd
● 1: lo ethernet UNKNOWN/UP (unmanaged)
MAC Address: 00:00:00:00:00:00
Addresses: 127.0.0.1/8
::1/128
Routes: ::1 metric 256
● 2: enp5s0 ethernet UP (networkd: enp5s0)
MAC Address: 00:16:3e:13:ae:10 (Red Hat, Inc.)
Addresses: 10.86.126.221/24 (dhcp)
fd42:bc43:e20e:8cf7:216:3eff:fe13:ae10/64
fe80::216:3eff:fe13:ae10/64 (link)
DNS Addresses: 10.86.126.1
fe80::216:3eff:feab:beb9
DNS Search: lxd
Routes: default via 10.86.126.1 from 10.86.126.221 metric 100 (dhcp)
10.86.126.0/24 from 10.86.126.221 metric 100 (link)
10.86.126.1 from 10.86.126.221 metric 100 (dhcp, link)
fd42:bc43:e20e:8cf7::/64 metric 100 (ra)
fe80::/64 metric 256
default via fe80::216:3eff:feab:beb9 metric 100 (ra)
● 3: enp6s0 ethernet DOWN (unmanaged)
MAC Address: 00:16:3e:0c:97:8a (Red Hat, Inc.)
```
# Checking the file where your configuration is stored
The configuration you just listed is stored at `/etc/netplan`. You can see the contents of the file with the command below:
```
cat /etc/netplan/50-cloud-init.yaml
```
You should see an output similar to this:
```yaml
# This file is generated from information provided by the datasource. Changes
# to it will not persist across an instance reboot. To disable cloud-init's
# network configuration capabilities, write a file
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
# network: {config: disabled}
network:
version: 2
ethernets:
enp5s0:
dhcp4: true
```
This file was automatically generated by `cloud-init` when the system was initialized. As noted in the comments, changes to this file will not persist.
# Enabling your second network interface with DHCP
There are basically 2 ways to create or change netplan configuration:
1) Using the `netplan set` command
2) Editing the YAML files manually
Let's see how you can enable your second network interface using both ways.
## Using `netplan set`
For simple tasks, you can use `netplan set` to change your configuration.
In the example below you are going to create a new YAML file called `second-interface.yaml` containing only the configuration needed to enable our second interfaces.
Considering your second network interface is `enp6s0`, run the command below:
```
netplan set --origin-hint second-interface ethernets.enp6s0.dhcp4=true
```
The command line parameter `--origin-hint` sets the name of the file where the configuration will be stored.
Now list the files in the directory `/etc/netplan`:
```
ls /etc/netplan
```
You should see the auto generated cloud-init file and a new file called `second-interface.yaml`:
```
root@netplan-lab0:~# ls /etc/netplan/
50-cloud-init.yaml second-interface.yaml
```
Use the command `cat` to see its content:
```
cat /etc/netplan/second-interface.yaml
```
```yaml
network:
version: 2
ethernets:
enp6s0:
dhcp4: true
```
You will notice it is very similar to the one generated by cloud-init.
Now use `netplan get` to see your full configuration:
```
netplan get
```
You should see an output similar to the one below with both ethernet interfaces:
```yaml
network:
version: 2
ethernets:
enp5s0:
dhcp4: true
enp6s0:
dhcp4: true
```
## Applying your new configuration
The command `netplan set` created the configuration for your second network interface but it wasn't applied to the running system.
Run the command below to see the current state of your second network interface:
```
ip address show enp6s0
```
You should see an output similar to the one below:
```
3: enp6s0: mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 00:16:3e:0c:97:8a brd ff:ff:ff:ff:ff:ff
```
As you can see, this interface has no IP address and its state is DOWN.
In order to apply the netplan configuration, you can use the command `netplan apply`.
Run the command below in your shell:
```
netplan apply
```
Now check again the state of the interface `enp6s0`:
```
ip address show enp6s0
```
You should see an output similar to this:
```
3: enp6s0: mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:16:3e:0c:97:8a brd ff:ff:ff:ff:ff:ff
inet 10.33.59.157/24 metric 100 brd 10.33.59.255 scope global dynamic enp6s0
valid_lft 3589sec preferred_lft 3589sec
inet6 fd42:ee65:61d0:abcb:216:3eff:fe0c:978a/64 scope global dynamic mngtmpaddr noprefixroute
valid_lft 3599sec preferred_lft 3599sec
inet6 fe80::216:3eff:fe0c:978a/64 scope link
valid_lft forever preferred_lft forever
```
You can also use `netplan status` to check the interface:
```
netplan status enp6s0
```
You should see an output similar to this:
```
Online state: online
DNS Addresses: 127.0.0.53 (stub)
DNS Search: lxd
● 3: enp6s0 ethernet UP (networkd: enp6s0)
MAC Address: 00:16:3e:0c:97:8a (Red Hat, Inc.)
Addresses: 10.33.59.157/24 (dhcp)
fd42:ee65:61d0:abcb:216:3eff:fe0c:978a/64
fe80::216:3eff:fe0c:978a/64 (link)
DNS Addresses: 10.33.59.1
fe80::216:3eff:fea1:585b
DNS Search: lxd
Routes: default via 10.33.59.1 from 10.33.59.157 metric 100 (dhcp)
10.33.59.0/24 from 10.33.59.157 metric 100 (link)
10.33.59.1 from 10.33.59.157 metric 100 (dhcp, link)
fd42:ee65:61d0:abcb::/64 metric 100 (ra)
fe80::/64 metric 256
2 inactive interfaces hidden. Use "--all" to show all.
```
As you can see, even though you haven't enabled DHCP for IPv6 on this interface, the network configuration backend (in this case systemd-networkd) enabled it anyway. But let's assume you want only IPv4.
Let's address this situation in the next exercise.
## Editing YAML files
For more complex configuration, you can just create or edit a new file yourself using your favorite text editor.
Continuing the exercise from the previous section, let's go ahead and disable automatic IPv6 configuration on your second interface. But this time let's do it by manually editing the YAML file.
Use your favorite text editor and open the file `/etc/netplan/second-interface.yaml`.
Add the configuration below to the interface configuration section:
```yaml
accept-ra: false
link-local: []
```
When you finish, it should look like this:
```yaml
network:
version: 2
ethernets:
enp6s0:
dhcp4: true
accept-ra: false
link-local: []
```
With this new configuration, the network configuration backend (systemd-networkd in this case) will not accept Route Advertisements and will not add the link-local address to our interface.
Now check your new configuration with the `netplan get` command:
```
netplan get
```
You should see something similar to this:
```yaml
network:
version: 2
ethernets:
enp5s0:
dhcp4: true
enp6s0:
dhcp4: true
accept-ra: false
link-local: []
```
Now use `netplan apply` to apply your new configuration:
```
netplan apply
```
And check your interface configuration:
```
ip address show enp6s0
```
You should see an output similar to this:
```
3: enp6s0: mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:16:3e:0c:97:8a brd ff:ff:ff:ff:ff:ff
inet 10.33.59.157/24 metric 100 brd 10.33.59.255 scope global dynamic enp6s0
valid_lft 3585sec preferred_lft 3585sec
```
And as you can see, now it only has an IPv4 address.
In this exercise you explored the `netplan set`, `netplan get`, `netplan apply` and `netplan status` commands. You also used some of the ethernet configuration options to get a network interface up and running with DHCP.
# Using static IP addresses
In this exercise you're going to add an static IP address to the second interface with a default route and DNS configuration.
Use your favorite text editor to open the file `/etc/netplan/second-interface.yaml` created previously. Change it so it will look like this:
```yaml
network:
version: 2
ethernets:
enp6s0:
dhcp4: false
dhcp6: false
accept-ra: false
link-local: []
addresses:
- 172.16.0.1/24
routes:
- to: default
via: 172.16.0.254
nameservers:
search:
- netplanlab.local
addresses:
- 172.16.0.254
- 172.16.0.253
```
The configuration above is what you'd expect in a desktop system for example. It defines the interface's IP address statically as `172.16.0.1/24`, a default route via gateway `172.16.0.254` and the DNS search domain and nameservers.
Now use `netplan get` to visualize all your network configuration:
```
netplan get
```
You should see an output similar to this:
```yaml
network:
version: 2
ethernets:
enp5s0:
dhcp4: true
enp6s0:
addresses:
- "172.16.0.1/24"
nameservers:
addresses:
- 172.16.0.254
- 172.16.0.253
search:
- netplanlab.local
dhcp4: false
dhcp6: false
accept-ra: false
routes:
- to: "default"
via: "172.16.0.254"
link-local: []
```
You will notice that it might be a little different than what you have defined in the YAML file. Some things might be in a different order for example.
The reason for that is that `netplan get` loads and parses your configuration before outputting it and the YAML parsing engine used by netplan might shuffle things around. Although, what you see from `netplan get` is equivalent to what you have in the file.
Now use `netplan apply` to apply the new configuration:
```
netplan apply
```
And check the interface's new state:
```
ip address show dev enp6s0
```
You should see something similar to this:
```
3: enp6s0: mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:16:3e:0c:97:8a brd ff:ff:ff:ff:ff:ff
inet 172.16.0.1/24 brd 172.16.0.255 scope global enp6s0
valid_lft forever preferred_lft forever
```
Check the routes associated to the interface:
```
ip route show dev enp6s0
```
You should see something similar to this:
```
default via 172.16.0.254 proto static
172.16.0.0/24 proto kernel scope link src 172.16.0.1
```
And check the DNS configuration:
```
netplan status enp6s0
```
You should see something similar to this:
```
Online state: online
DNS Addresses: 127.0.0.53 (stub)
DNS Search: netplanlab.local
lxd
● 3: enp6s0 ethernet UP (networkd: enp6s0)
MAC Address: 00:16:3e:0c:97:8a (Red Hat, Inc.)
Addresses: 172.16.0.1/24
DNS Addresses: 172.16.0.254
172.16.0.253
DNS Search: netplanlab.local
Routes: default via 172.16.0.254 (static)
172.16.0.0/24 from 172.16.0.1 (link)
2 inactive interfaces hidden. Use "--all" to show all.
```
# Matching the interface by MAC address
Sometimes you can't rely on the interface names to apply configuration to them. Changes in the system might cause a change in their names, such as when you move an interface card from a PCI slot to another.
In this exercise you will use the `match` keyword to locate the device based on its MAC address and also set a more meaningful name to the interface.
Let's assume that your second interface is connected to the Netplan ISP internet provider company and you want to identify it as such.
First identify its MAC address:
```
ip link show enp6s0
```
In the output, the MAC address is the number in front of the `link/ether` property.
```
3: enp6s0: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:16:3e:0c:97:8a brd ff:ff:ff:ff:ff:ff
```
Use your favorite text editor to open the file `/etc/netplan/second-interface.yaml` and make the following changes:
```yaml
network:
version: 2
ethernets:
netplan-isp-interface:
match:
macaddress: 00:16:3e:0c:97:8a
set-name: netplan-isp
dhcp4: false
dhcp6: false
accept-ra: false
link-local: []
addresses:
- 172.16.0.1/24
routes:
- to: default
via: 172.16.0.254
nameservers:
search:
- netplanlab.local
addresses:
- 172.16.0.254
- 172.16.0.253
```
These are the important changes in this exercise:
```yaml
ethernets:
netplan-isp-interface:
match:
macaddress: 00:16:3e:0c:97:8a
set-name: netplan-isp
```
Note that, as you are now matching the interface by its MAC address, you are free to identify it with a different name. It makes it easier to read and find information in the YAML file.
After changing the file, apply your new configuration:
```
netplan apply
```
Now list your interfaces:
```
ip link show
```
As you can see, your interface is now called `netplan-isp`.
```
1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp5s0: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:16:3e:13:ae:10 brd ff:ff:ff:ff:ff:ff
3: netplan-isp: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:16:3e:0c:97:8a brd ff:ff:ff:ff:ff:ff
```
# Creating a link aggregation
Let's suppose now that you need to configure your system to connect to your
ISP links via a link aggregation. On Linux you can do that with a `bond`
virtual interface.
On Netplan, an interface of type `bond` can be created inside a `bonds` mapping.
Now that the traffic will flow through the link aggregation, you will move
all the addressing configuration to the bond itself.
You can define a list of interfaces that will be attached to the bond. In our
simple scenario, we have a single one.
Use your favorite text editor to open the file `/etc/netplan/second-interface.yaml` and make the following changes:
```yaml
network:
version: 2
ethernets:
netplan-isp-interface:
dhcp4: false
dhcp6: false
match:
macaddress: 00:16:3e:0c:97:8a
set-name: netplan-isp
bonds:
isp-bond0:
interfaces:
- netplan-isp-interface
dhcp4: false
dhcp6: false
accept-ra: false
link-local: []
addresses:
- 172.16.0.1/24
routes:
- to: default
via: 172.16.0.254
nameservers:
search:
- netplanlab.local
addresses:
- 172.16.0.254
- 172.16.0.253
```
Note that you can reference the interface used in the bond by the name you
defined for it in the `ethernets` section.
Now use `netplan apply` to apply your changes
```
netplan apply
```
Now your system has a new interface called `isp-bond0`. Use the command
`ip address show isp-bond0` or `netplan status` to check its state:
```
netplan status isp-bond0
```
You should see an output similar to the one below:
```
Online state: online
DNS Addresses: 127.0.0.53 (stub)
DNS Search: lxd
netplanlab.local
● 4: isp-bond0 bond UP (networkd: isp-bond0)
MAC Address: b2:6b:19:b1:9a:86
Addresses: 172.16.0.1/24
DNS Addresses: 172.16.0.254
172.16.0.253
DNS Search: netplanlab.local
Routes: default via 172.16.0.254 (static)
172.16.0.0/24 from 172.16.0.1 (link)
3 inactive interfaces hidden. Use "--all" to show all.
```
netplan-0.107.1/doc/netplan-yaml.md 0000664 0000000 0000000 00000211116 14536110317 0017014 0 ustar 00root root 0000000 0000000 ---
title: "YAML configuration"
---
## Top-level configuration structure
The general structure of a Netplan YAML file is shown below.
```yaml
network:
version: NUMBER
renderer: STRING
bonds: MAPPING
bridges: MAPPING
dummy-devices: MAPPING
ethernets: MAPPING
modems: MAPPING
tunnels: MAPPING
virtual-ethernets: MAPPING
vlans: MAPPING
vrfs: MAPPING
wifis: MAPPING
nm-devices: MAPPING
```
- **version** (number)
> Defines what version of the configuration format is used. The only value supported is `2`. Defaults to `2` if not defined.
- **renderer** (scalar)
> Defines what network configuration tool will be used to set up your configuration. Valid values are `networkd` and `NetworkManager`. Defaults to `networkd` if not defined.
- [**bonds**](#properties-for-device-type-bonds) (mapping)
> Creates and configures link aggregation (bonding) devices.
- [**bridges**](#properties-for-device-type-bridges) (mapping)
> Creates and configures bridge devices.
- [**dummy-devices**](#properties-for-device-type-dummy-devices) (mapping) – since **0.107**
> Creates and configures virtual devices.
- [**ethernets**](#properties-for-device-type-ethernets) (mapping)
> Configures physical Ethernet interfaces.
- [**modems**](#properties-for-device-type-modems) (mapping)
> Configures modems
- [**tunnels**](#properties-for-device-type-tunnels) (mapping)
> Creates and configures different types of virtual tunnels.
- [**virtual-ethernets**](#properties-for-device-type-virtual-ethernets) (mapping) – since **0.107**
> Creates and configures Virtual Ethernet (veth) devices.
- [**vlans**](#properties-for-device-type-vlans) (mapping)
> Creates and configures VLANs.
- [**vrfs**](#properties-for-device-type-vrfs) (mapping)
> Configures Virtual Routing and Forwarding (VRF) devices.
- [**wifis**](#properties-for-device-type-wifis) (mapping)
> Configures physical Wifi interfaces as client, adhoc or access point.
- [**nm-devices**](#properties-for-device-type-nm-devices) (mapping)
> `nm-devices` are used in situations where Netplan doesn't support the connection type. The raw configuration expected by NetworkManager can be defined and will be passed as is (passthrough) to the `.nmconnection` file. Users will not normally use this type of device.
All the properties for all the device types will be described in the next sections.
## Properties for physical device types
These properties are used with physical devices such as Ethernet and Wifi network interfaces.
**Note:** Some options will not work reliably for devices matched by name only
and rendered by networkd, due to interactions with device renaming in udev.
Match devices by MAC when setting options like: `wakeonlan` or `*-offload`.
- **match** (mapping)
> This selects a subset of available physical devices by various hardware
> properties. The following configuration will then apply to all matching
> devices, as soon as they appear. *All* specified properties must match.
- **name** (scalar)
> Current interface name. Globs are supported, and the primary use case for
> matching on names, as selecting one fixed name can be more easily achieved
> with having no `match:` at all and just using the ID (see above).
> (`NetworkManager`: as of v1.14.0)
- **macaddress** (scalar)
> Device's 6-byte permanent MAC address in the form "XX:XX:XX:XX:XX:XX" or
> 20 bytes for InfiniBand devices (IPoIB). Globs are not allowed.
> This doesn't match virtual MAC addresses for veth, bridge, bond, vlan, ...
- **driver** (scalar or sequence of scalars) – sequence since **0.104**
> Kernel driver name, corresponding to the `DRIVER` udev property.
> A sequence of globs is supported, any of which must match.
> Matching on driver is *only* supported with networkd.
Examples:
- All cards on second PCI bus:
```yaml
network:
ethernets:
myinterface:
match:
name: enp2*
```
- Fixed MAC address:
```yaml
network:
ethernets:
interface0:
match:
macaddress: 11:22:33:AA:BB:FF
```
- First card of driver ``ixgbe``:
```yaml
network:
ethernets:
nic0:
match:
driver: ixgbe
name: en*s0
```
- First card with a driver matching ``bcmgenet`` or ``smsc*``:
```yaml
network:
ethernets:
nic0:
match:
driver: ["bcmgenet", "smsc*"]
name: en*
```
- **set-name** (scalar)
> When matching on unique properties such as path or MAC, or with additional
> assumptions such as "there will only ever be one wifi device", match rules
> can be written so that they only match one device. Then this property can be
> used to give that device a more specific/desirable/nicer name than the
> default from udev's ifnames. Any additional device that satisfies the match
> rules will then fail to get renamed and keep the original kernel name (and
> dmesg will show an error).
- **wakeonlan** (bool)
> Enable wake on LAN. Off by default.
- **emit-lldp** (bool) – since **0.99**
> (networkd backend only) Whether to emit LLDP packets. Off by default.
- **receive-checksum-offload** (bool) – since **0.104**
> (networkd backend only) If set to true (false), the hardware offload for
> checksumming of ingress network packets is enabled (disabled). When unset,
> the kernel's default will be used.
- **transmit-checksum-offload** (bool) – since **0.104**
> (networkd backend only) If set to true (false), the hardware offload for
> checksumming of egress network packets is enabled (disabled). When unset,
> the kernel's default will be used.
- **tcp-segmentation-offload** (bool) – since **0.104**
> (networkd backend only) If set to true (false), the TCP Segmentation
> Offload (TSO) is enabled (disabled). When unset, the kernel's default will
> be used.
- **tcp6-segmentation-offload** (bool) – since **0.104**
> (networkd backend only) If set to true (false), the TCP6 Segmentation
> Offload (tx-tcp6-segmentation) is enabled (disabled). When unset, the
> kernel's default will be used.
- **generic-segmentation-offload** (bool) – since **0.104**
> (networkd backend only) If set to true (false), the Generic Segmentation
> Offload (GSO) is enabled (disabled). When unset, the kernel's default will
> be used.
- **generic-receive-offload** (bool) – since **0.104**
> (networkd backend only) If set to true (false), the Generic Receive
> Offload (GRO) is enabled (disabled). When unset, the kernel's default will
> be used.
- **large-receive-offload** (bool) – since **0.104**
> (networkd backend only) If set to true (false), the Large Receive Offload
> (LRO) is enabled (disabled). When unset, the kernel's default will
> be used.
- **openvswitch** (mapping) – since **0.100**
> This provides additional configuration for the openvswitch network device.
> If Open vSwitch is not available on the system, netplan treats the presence
> of `openvswitch` configuration as an error.
>
> Any supported network device that is declared with the `openvswitch`
> mapping (or any bond/bridge that includes an interface with an openvswitch
> configuration) will be created in openvswitch instead of the defined
> renderer. In the case of a `vlan` definition declared the same way,
> netplan will create a fake VLAN bridge in openvswitch with the requested
> `vlan` properties.
- **external-ids** (mapping) – since **0.100**
> Passed-through directly to Open vSwitch
- **other-config** (mapping) – since **0.100**
> Passed-through directly to Open vSwitch
- **lacp** (scalar) – since **0.100**
> Valid for bond interfaces. Accepts `active`, `passive` or `off` (the
> default).
- **fail-mode** (scalar) – since **0.100**
> Valid for bridge interfaces. Accepts `secure` or `standalone` (the
> default).
- **mcast-snooping** (bool) – since **0.100**
> Valid for bridge interfaces. False by default.
- **protocols** (sequence of scalars) – since **0.100**
> Valid for bridge interfaces or the network section. List of protocols to
> be used when negotiating a connection with the controller. Accepts
> `OpenFlow10`, `OpenFlow11`, `OpenFlow12`, `OpenFlow13`, `OpenFlow14`,
> and `OpenFlow15`.
- **rstp** (bool) – since **0.100**
> Valid for bridge interfaces. False by default.
- **controller** (mapping) – since **0.100**
> Valid for bridge interfaces. Specify an external OpenFlow controller.
- **addresses** (sequence of scalars)
> Set the list of addresses to use for the controller targets. The
> syntax of these addresses is as defined in ovs-vsctl(8). Example:
> addresses: `[tcp:127.0.0.1:6653, "ssl:[fe80::1234%eth0]:6653"]`
- **connection-mode** (scalar)
> Set the connection mode for the controller. Supported options are
> `in-band` and `out-of-band`. The default is `in-band`.
- **ports** (sequence of sequence of scalars) – since **0.100**
> Open vSwitch patch ports. Each port is declared as a pair of names
> which can be referenced as interfaces in dependent virtual devices
> (bonds, bridges).
Example:
```yaml
openvswitch:
ports:
- [patch0-1, patch1-0]
```
- **ssl** (mapping) – since **0.100**
> Valid for global `openvswitch` settings. Options for configuring SSL
> server endpoint for the switch.
- **ca-cert** (scalar)
> Path to a file containing the CA certificate to be used.
- **certificate** (scalar)
> Path to a file containing the server certificate.
- **private-key** (scalar)
> Path to a file containing the private key for the server.
## Properties for all device types
- **renderer** (scalar)
> Use the given networking backend for this definition. Currently supported
> are `networkd` and `NetworkManager`. This property can be specified globally
> in `network:`, for a device type (in e. g. `ethernets:`) or
> for a particular device definition. Default is `networkd`.
>
> (Since 0.99) The `renderer` property has one additional acceptable value for
> vlan objects (i. e. defined in `vlans:`): `sriov`. If a vlan is defined with
> the `sriov` renderer for an SR-IOV Virtual Function interface, this causes
> netplan to set up a hardware VLAN filter for it. There can be only one
> defined per VF.
- **dhcp4** (bool)
> Enable DHCP for IPv4. Off by default.
- **dhcp6** (bool)
> Enable DHCP for IPv6. Off by default. This covers both stateless DHCP -
> where the DHCP server supplies information like DNS nameservers but not the
> IP address - and stateful DHCP, where the server provides both the address
> and the other information.
>
> If you are in an IPv6-only environment with completely stateless
> auto-configuration (SLAAC with RDNSS), this option can be set to cause the
> interface to be brought up. (Setting accept-ra alone is not sufficient.)
> Auto-configuration will still honor the contents of the router
> advertisement and only use DHCP if requested in the RA.
>
> Note that **`rdnssd`**(8) is required to use RDNSS with networkd. No extra
> software is required for NetworkManager.
- **ipv6-mtu** (scalar) – since **0.98**
> Set the IPv6 MTU (only supported with `networkd` backend). Note
> that needing to set this is an unusual requirement.
>
> **Requires feature: ipv6-mtu**
- **ipv6-privacy** (bool)
> Enable IPv6 Privacy Extensions (RFC 4941) for the specified interface, and
> prefer temporary addresses. Defaults to false - no privacy extensions. There
> is currently no way to have a private address but prefer the public address.
- **link-local** (sequence of scalars)
> Configure the link-local addresses to bring up. Valid options are 'ipv4'
> and 'ipv6', which respectively allow enabling IPv4 and IPv6 link local
> addressing. If this field is not defined, the default is to enable only
> IPv6 link-local addresses. If the field is defined but configured as an
> empty set, IPv6 link-local addresses are disabled as well as IPv4 link-
> local addresses.
>
> This feature enables or disables link-local addresses for a protocol, but
> the actual implementation differs per backend. On networkd, this directly
> changes the behavior and may add an extra address on an interface. When
> using the NetworkManager backend, enabling link-local has no effect if the
> interface also has DHCP enabled.
Examples:
- Enable only IPv4 link-local: `link-local: [ ipv4 ]`
- Enable all link-local addresses: `link-local: [ ipv4, ipv6 ]`
- Disable all link-local addresses: `link-local: [ ]`
- **ignore-carrier** (bool) – since **0.104**
> (networkd backend only) Allow the specified interface to be configured even
> if it has no carrier.
- **critical** (bool)
> Designate the connection as "critical to the system", meaning that special
> care will be taken by to not release the assigned IP when the daemon is
> restarted. (not recognized by NetworkManager)
- **dhcp-identifier** (scalar)
> (networkd backend only) Sets the source of DHCPv4 client identifier. If
> `mac` is specified, the MAC address of the link is used. If this option is
> omitted, or if `duid` is specified, networkd will generate an
> RFC4361-compliant client identifier for the interface by combining the
> link's IAID and DUID.
- **dhcp4-overrides** (mapping)
> (networkd backend only) Overrides default DHCP behavior; see the
> `DHCP Overrides` section below.
- **dhcp6-overrides** (mapping)
> (networkd backend only) Overrides default DHCP behavior; see the
> `DHCP Overrides` section below.
- **accept-ra** (bool)
> Accept Router Advertisement that would have the kernel configure IPv6 by
> itself. When enabled, accept Router Advertisements. When disabled, do not
> respond to Router Advertisements. If unset use the host kernel default
> setting.
- **addresses** (sequence of scalars and mappings)
> Add static addresses to the interface in addition to the ones received
> through DHCP or RA. Each sequence entry is in CIDR notation, i. e. of the
> form `addr/prefixlen`. `addr` is an IPv4 or IPv6 address as recognized
> by **`inet_pton`**(3) and `prefixlen` the number of bits of the subnet.
>
> For virtual devices (bridges, bonds, vlan) if there is no address
> configured and DHCP is disabled, the interface may still be brought online,
> but will not be addressable from the network.
>
> In addition to the addresses themselves one can specify configuration
> parameters as mappings. Current supported options are:
- **lifetime** (scalar) – since **0.100**
> Default: ``forever``. This can be ``forever`` or ``0`` and corresponds
> to the ``PreferredLifetime`` option in ``systemd-networkd``'s Address
> section. Currently supported on the ``networkd`` backend only.
- **label** (scalar) – since **0.100**
> An IP address label, equivalent to the ``ip address label``
> command. Currently supported on the ``networkd`` backend only.
Examples:
- Simple: ``addresses: [192.168.14.2/24, "2001:1::1/64"]``
- Advanced:
```yaml
network:
ethernets:
eth0:
addresses:
- "10.0.0.15/24":
lifetime: 0
label: "maas"
- "2001:1::1/64"
```
- **ipv6-address-generation** (scalar) – since **0.99**
> Configure method for creating the address for use with RFC4862 IPv6
> Stateless Address Auto-configuration (only supported with `NetworkManager`
> backend). Possible values are `eui64` or `stable-privacy`.
- **ipv6-address-token** (scalar) – since **0.100**
> Define an IPv6 address token for creating a static interface identifier for
> IPv6 Stateless Address Auto-configuration. This is mutually exclusive with
> `ipv6-address-generation`.
- **gateway4**, **gateway6** (scalar)
> Deprecated, see `Default routes`.
> Set default gateway for IPv4/6, for manual address configuration. This
> requires setting `addresses` too. Gateway IPs must be in a form
> recognized by **`inet_pton`**(3). There should only be a single gateway
> per IP address family set in your global config, to make it unambiguous.
> If you need multiple default routes, please define them via
> `routing-policy`.
Examples
- IPv4: `gateway4: 172.16.0.1`
- IPv6: `gateway6: "2001:4::1"`
- **nameservers** (mapping)
> Set DNS servers and search domains, for manual address configuration. There
> are two supported fields: `addresses:` is a list of IPv4 or IPv6 addresses
> similar to `gateway*`, and `search:` is a list of search domains.
Example:
```yaml
network:
ethernets:
id0:
[...]
nameservers:
search: [lab, home]
addresses: [8.8.8.8, "FEDC::1"]
```
- **macaddress** (scalar)
> Set the device's MAC address. The MAC address must be in the form
> "XX:XX:XX:XX:XX:XX".
>
> **Note:** This will not work reliably for devices matched by name
> only and rendered by networkd, due to interactions with device
> renaming in udev. Match devices by MAC when setting MAC addresses.
Example:
```yaml
network:
ethernets:
id0:
match:
macaddress: 52:54:00:6b:3c:58
[...]
macaddress: 52:54:00:6b:3c:59
```
- **mtu** (scalar)
> Set the Maximum Transmission Unit for the interface. The default is 1500.
> Valid values depend on your network interface.
>
> **Note:** This will not work reliably for devices matched by name
> only and rendered by networkd, due to interactions with device
> renaming in udev. Match devices by MAC when setting MTU.
- **optional** (bool)
> An optional device is not required for booting. Normally, networkd will
> wait some time for device to become configured before proceeding with
> booting. However, if a device is marked as optional, networkd will not wait
> for it. This is *only* supported by networkd, and the default is false.
Example:
```yaml
network:
ethernets:
eth7:
# this is plugged into a test network that is often
# down - don't wait for it to come up during boot.
dhcp4: true
optional: true
```
- **optional-addresses** (sequence of scalars)
> Specify types of addresses that are not required for a device to be
> considered online. This changes the behavior of backends at boot time to
> avoid waiting for addresses that are marked optional, and thus consider
> the interface as "usable" sooner. This does not disable these addresses,
> which will be brought up anyway.
Example:
```yaml
network:
ethernets:
eth7:
dhcp4: true
dhcp6: true
optional-addresses: [ ipv4-ll, dhcp6 ]
```
- **activation-mode** (scalar) – since **0.103**
> Allows specifying the management policy of the selected interface. By
> default, netplan brings up any configured interface if possible. Using the
> `activation-mode` setting users can override that behavior by either
> specifying `manual`, to hand over control over the interface state to the
> administrator or (for networkd backend *only*) `off` to force the link
> in a down state at all times. Any interface with `activation-mode`
> defined is implicitly considered `optional`.
> Supported officially as of `networkd` v248+.
Example:
```yaml
network:
ethernets:
eth1:
# this interface will not be put into an UP state automatically
dhcp4: true
activation-mode: manual
```
- **routes** (sequence of mappings)
> Configure static routing for the device; see the `Routing` section below.
- **routing-policy** (sequence of mappings)
> Configure policy routing for the device; see the `Routing` section below.
- **neigh-suppress** (scalar) – since **0.105**
> Takes a boolean. Configures whether ARP and ND neighbor suppression is
> enabled for this port. When unset, the kernel's default will be used.
## DHCP Overrides
Several DHCP behavior overrides are available. Most currently only have any
effect when using the `networkd` backend, with the exception of `use-routes`
and `route-metric`.
Overrides only have an effect if the corresponding `dhcp4` or `dhcp6` is
set to `true`.
If both `dhcp4` and `dhcp6` are `true`, the `networkd` backend requires
that `dhcp4-overrides` and `dhcp6-overrides` contain the same keys and
values. If the values do not match, an error will be shown and the network
configuration will not be applied.
When using the NetworkManager backend, different values may be specified for
`dhcp4-overrides` and `dhcp6-overrides`, and will be applied to the DHCP
client processes as specified in the netplan YAML.
- **dhcp4-overrides**, **dhcp6-overrides** (mapping)
> The `dhcp4-overrides` and `dhcp6-override`` mappings override the
> default DHCP behavior.
- **use-dns** (bool)
> Default: `true`. When `true`, the DNS servers received from the
> DHCP server will be used and take precedence over any statically
> configured ones. Currently only has an effect on the `networkd`
> backend.
- **use-ntp** (bool)
> Default: `true`. When `true`, the NTP servers received from the
> DHCP server will be used by systemd-timesyncd and take precedence
> over any statically configured ones. Currently only has an effect on
> the `networkd` backend.
- **send-hostname** (bool)
> Default: `true`. When `true`, the machine's hostname will be sent
> to the DHCP server. Currently only has an effect on the `networkd`
> backend.
- **use-hostname** (bool)
> Default: `true`. When `true`, the hostname received from the DHCP
> server will be set as the transient hostname of the system. Currently
> only has an effect on the `networkd` backend.
- **use-mtu** (bool)
> Default: `true`. When `true`, the MTU received from the DHCP
> server will be set as the MTU of the network interface. When `false`,
> the MTU advertised by the DHCP server will be ignored. Currently only
> has an effect on the `networkd` backend.
- **hostname** (scalar)
> Use this value for the hostname which is sent to the DHCP server,
> instead of machine's hostname. Currently only has an effect on the
> `networkd` backend.
- **use-routes** (bool)
> Default: `true`. When `true`, the routes received from the DHCP
> server will be installed in the routing table normally. When set to
> `false`, routes from the DHCP server will be ignored: in this case,
> the user is responsible for adding static routes if necessary for
> correct network operation. This allows users to avoid installing a
> default gateway for interfaces configured via DHCP. Available for
> both the `networkd` and `NetworkManager` backends.
- **route-metric** (scalar)
> Use this value for default metric for automatically-added routes.
> Use this to prioritize routes for devices by setting a lower metric
> on a preferred interface. Available for both the `networkd` and
> `NetworkManager` backends.
- **use-domains** (scalar) – since **0.98**
> Takes a boolean, or the special value "route". When true, the domain
> name received from the DHCP server will be used as DNS search domain
> over this link, similar to the effect of the Domains= setting. If set
> to "route", the domain name received from the DHCP server will be
> used for routing DNS queries only, but not for searching, similar to
> the effect of the Domains= setting when the argument is prefixed with
> "~".
>
> **Requires feature: dhcp-use-domains**
## Routing
Complex routing is possible with netplan. Standard static routes as well
as policy routing using routing tables are supported via the `networkd`
backend.
These options are available for all types of interfaces.
### Default routes
The most common need for routing concerns the definition of default routes to
reach the wider Internet. Those default routes can only defined once per IP
family and routing table. A typical example would look like the following:
```yaml
network:
ethernets:
eth0:
[...]
routes:
- to: default # could be 0.0.0.0/0 optionally
via: 10.0.0.1
metric: 100
on-link: true
- to: default # could be ::/0 optionally
via: cf02:de:ad:be:ef::2
eth1:
[...]
routes:
- to: default
via: 172.134.67.1
metric: 100
on-link: true
# Not on the main routing table,
# does not conflict with the eth0 default route
table: 76
```
- **routes** (mapping)
> The `routes` block defines standard static routes for an interface.
> At least `to` must be specified. If type is `local` or `nat` a
> default scope of `host` is assumed.
> If type is `unicast` and no gateway (`via`) is given or type is
> `broadcast`, `multicast` or `anycast` a default scope of `link`
> is assumed. Otherwise, a ``global`` scope is the default setting.
>
> For `from`, `to`, and `via`, both IPv4 and IPv6 addresses are
> recognized, and must be in the form `addr/prefixlen` or `addr`.
- **from** (scalar)
> Set a source IP address for traffic going through the route.
> (`NetworkManager`: as of v1.8.0)
- **to** (scalar)
> Destination address for the route.
- **via** (scalar)
> Address to the gateway to use for this route.
- **on-link** (bool)
> When set to "true", specifies that the route is directly connected
> to the interface.
> (`NetworkManager`: as of v1.12.0 for IPv4 and v1.18.0 for IPv6)
- **metric** (scalar)
> The relative priority of the route. Must be a positive integer value.
- **type** (scalar)
> The type of route. Valid options are "unicast" (default), "anycast",
> "blackhole", "broadcast", "local", "multicast", "nat", "prohibit",
> "throw", "unreachable" or "xresolve".
- **scope** (scalar)
> The route scope, how wide-ranging it is to the network. Possible
> values are "global", "link", or "host". Applies to IPv4 only.
- **table** (scalar)
> The table number to use for the route. In some scenarios, it may be
> useful to set routes in a separate routing table. It may also be used
> to refer to routing policy rules which also accept a `table`
> parameter. Allowed values are positive integers starting from 1.
> Some values are already in use to refer to specific routing tables:
> see `/etc/iproute2/rt_tables`.
> (`NetworkManager`: as of v1.10.0)
- **mtu** (scalar) – since **0.101**
> The MTU to be used for the route, in bytes. Must be a positive integer
> value.
- **congestion-window** (scalar) – since **0.102**
> The congestion window to be used for the route, represented by number
> of segments. Must be a positive integer value.
- **advertised-receive-window** (scalar) – since **0.102**
> The receive window to be advertised for the route, represented by
> number of segments. Must be a positive integer value.
- **routing-policy** (mapping)
> The `routing-policy` block defines extra routing policy for a network,
> where traffic may be handled specially based on the source IP, firewall
> marking, etc.
>
> For `from`, `to`, both IPv4 and IPv6 addresses are recognized, and
> must be in the form `addr/prefixlen` or `addr`.
- **from** (scalar)
> Set a source IP address to match traffic for this policy rule.
- **to** (scalar)
> Match on traffic going to the specified destination.
- **table** (scalar)
> The table number to match for the route. In some scenarios, it may be
> useful to set routes in a separate routing table. It may also be used
> to refer to routes which also accept a `table` parameter.
> Allowed values are positive integers starting from 1.
> Some values are already in use to refer to specific routing tables:
> see `/etc/iproute2/rt_tables`.
- **priority** (scalar)
> Specify a priority for the routing policy rule, to influence the order
> in which routing rules are processed. A higher number means lower
> priority: rules are processed in order by increasing priority number.
- **mark** (scalar)
> Have this routing policy rule match on traffic that has been marked
> by the iptables firewall with this value. Allowed values are positive
> integers starting from 1.
- **type-of-service** (scalar)
> Match this policy rule based on the type of service number applied to
> the traffic.
## Authentication
Netplan supports advanced authentication settings for ethernet and wifi
interfaces, as well as individual wifi networks, by means of the `auth` block.
- **auth** (mapping)
> Specifies authentication settings for a device of type `ethernets:`, or
> an `access-points:` entry on a `wifis:` device.
>
> The `auth` block supports the following properties:
- **key-management** (scalar)
> The supported key management modes are `none` (no key management);
> `psk` (WPA with pre-shared key, common for home wifi); `eap` (WPA
> with EAP, common for enterprise wifi); `eap-sha256` (used with WPA3-Enterprise);
> `eap-suite-b-192` (used with WPA3-Enterprise); `sae` (used by WPA3);
> and `802.1x` (used primarily for wired Ethernet connections).
- **password** (scalar)
> The password string for EAP, or the pre-shared key for WPA-PSK.
The following properties can be used if `key-management` is `eap`
or `802.1x`:
- **method** (scalar)
> The EAP method to use. The supported EAP methods are `tls` (TLS),
> `peap` (Protected EAP), `leap` (Lightweight EAP), `pwd` (EAP Password)
> and `ttls` (Tunneled TLS).
- **identity** (scalar)
> The identity to use for EAP.
- **anonymous-identity** (scalar)
> The identity to pass over the unencrypted channel if the chosen EAP
> method supports passing a different tunnelled identity.
- **ca-certificate** (scalar)
> Path to a file with one or more trusted certificate authority (CA)
> certificates.
- **client-certificate** (scalar)
> Path to a file containing the certificate to be used by the client
> during authentication.
- **client-key** (scalar)
> Path to a file containing the private key corresponding to
> `client-certificate`.
- **client-key-password** (scalar)
> Password to use to decrypt the private key specified in
> `client-key` if it is encrypted.
- **phase2-auth** (scalar) – since **0.99**
> Phase 2 authentication mechanism.
## Properties for device type `ethernets:`
**Status**: Optional.
**Purpose**: Use the `ethernets` key to configure Ethernet interfaces.
**Structure**: The key consists of a mapping of Ethernet interface IDs. Each
`ethernet` has a number of configuration options. You don't need to define each
interface by their name inside the `ethernets` mapping. You can use any ID that
describes the interface and match the actual network card using the `match` key.
The general configuration structure for Ethernets is shown below.
```yaml
network:
ethernets:
device-id:
...
```
`device-id` is the interface identifier. If you use the interface name as the ID, Netplan will match that interface.
Consider the example below. In this case, an interface called `eth0` will be configured with DHCP.
```yaml
network:
ethernets:
eth0:
dhcp4: true
```
The `device-id` can be any descriptive name your find meaningful. Although, if it doesn't match a real interface name, you must use the property `match` to identify the device you want to configure.
The example below defines an Ethernet connection called `isp-interface` (supposedly an external interface connected to the Internet Service Provider) and uses `match` to apply the configuration to the physical device with MAC address `aa:bb:cc:00:11:22`.
```yaml
network:
ethernets:
isp-interface:
match:
macaddress: aa:bb:cc:00:11:22
dhcp4: true
```
Ethernet device definitions, beyond common ones described above, also support
some additional properties that can be used for SR-IOV devices.
- **link** (scalar) – since **0.99**
> (SR-IOV devices only) The `link` property declares the device as a
> Virtual Function of the selected Physical Function device, as identified
> by the given netplan id.
Example:
```yaml
network:
ethernets:
enp1: {...}
enp1s16f1:
link: enp1
```
- **virtual-function-count** (scalar) – since **0.99**
> (SR-IOV devices only) In certain special cases VFs might need to be
> configured outside of netplan. For such configurations
> `virtual-function-count` can be optionally used to set an explicit number of
> Virtual Functions for the given Physical Function. If unset, the default is
> to create only as many VFs as are defined in the netplan configuration. This
> should be used for special cases only.
>
> **Requires feature: sriov**
- **embedded-switch-mode** (scalar) – since **0.104**
> (SR-IOV devices only) Change the operational mode of the embedded switch
> of a supported SmartNIC PCI device (e.g. Mellanox ConnectX-5). Possible
> values are `switchdev` or `legacy`, if unspecified the vendor's
> default configuration is used.
>
> **Requires feature: eswitch-mode**
- **delay-virtual-functions-rebind** (bool) – since **0.104**
> (SR-IOV devices only) Delay rebinding of SR-IOV virtual functions to its
> driver after changing the embedded-switch-mode setting to a later stage.
> Can be enabled when bonding/VF LAG is in use. Defaults to `false`.
>
> **Requires feature: eswitch-mode**
- **infiniband-mode** (scalar) – since **0.105**
> (InfiniBand devices only) Change the operational mode of a IPoIB device.
> Possible values are `datagram` or `connected`. If unspecified the
> kernel's default configuration is used.
>
> **Requires feature: infiniband**
## Properties for device type `modems:`
**Status**: Optional.
**Purpose**: Use the `modems` key to configure Modem interfaces. GSM/CDMA modem
configuration is only supported for the `NetworkManager` backend.
`systemd-networkd` does not support modems.
**Structure**: The key consists of a mapping of Modem IDs. Each `modem` has a
number of configuration options. The general configuration structure for Modems
is shown below.
```yaml
network:
version: 2
renderer: NetworkManager
modems:
cdc-wdm1:
mtu: 1600
apn: ISP.CINGULAR
username: ISP@CINGULARGPRS.COM
password: CINGULAR1
number: "*99#"
network-id: 24005
device-id: da812de91eec16620b06cd0ca5cbc7ea25245222
pin: 2345
sim-id: 89148000000060671234
sim-operator-id: 310260
```
**Requires feature: modems**
- **apn** (scalar) – since **0.99**
> Set the carrier APN (Access Point Name). This can be omitted if
> `auto-config` is enabled.
- **auto-config** (bool) – since **0.99**
> Specify whether to try and auto-configure the modem by doing a lookup of
> the carrier against the Mobile Broadband Provider database. This may not
> work for all carriers.
- **device-id** (scalar) – since **0.99**
> Specify the device ID (as given by the WWAN management service) of the
> modem to match. This can be found using `mmcli`.
- **network-id** (scalar) – since **0.99**
> Specify the Network ID (GSM LAI format). If this is specified, the device
> will not roam networks.
- **number** (scalar) – since **0.99**
> The number to dial to establish the connection to the mobile broadband
> network. (Deprecated for GSM)
- **password** (scalar) – since **0.99**
> Specify the password used to authenticate with the carrier network. This
> can be omitted if `auto-config` is enabled.
- **pin** (scalar) – since **0.99**
> Specify the SIM PIN to allow it to operate if a PIN is set.
- **sim-id** (scalar) – since **0.99**
> Specify the SIM unique identifier (as given by the WWAN management service)
> which this connection applies to. If given, the connection will apply to
> any device also allowed by `device-id` which contains a SIM card matching
> the given identifier.
- **sim-operator-id** (scalar) – since **0.99**
> Specify the MCC/MNC string (such as "310260" or "21601") which identifies
> the carrier that this connection should apply to. If given, the connection
> will apply to any device also allowed by `device-id` and `sim-id`
> which contains a SIM card provisioned by the given operator.
- **username** (scalar) – since **0.99**
> Specify the username used to authenticate with the carrier network. This
> can be omitted if `auto-config` is enabled.
## Properties for device type `wifis:`
**Status**: Optional.
**Purpose**: Use the `wifis` key to configure WiFi access points.
**Structure**: The key consists of a mapping of WiFi IDs. Each `wifi` has a
number of configuration options. The general configuration structure for WiFis
is shown below.
```yaml
network:
version: 2
wifis:
wlp0s1:
access-points:
"network_ssid_name":
password: "**********"
```
Note that `systemd-networkd` does not natively support wifi, so you need
wpasupplicant installed if you let the `networkd` renderer handle wifi.
- **access-points** (mapping)
> This provides pre-configured connections to NetworkManager. Note that
> users can of course select other access points/SSIDs. The keys of the
> mapping are the SSIDs, and the values are mappings with the following
> supported properties:
- **password** (scalar)
> Enable WPA/WPA2 authentication and set the passphrase for it. If neither
> this nor an `auth` block are given, the network is assumed to be
> open. The setting
> ```yaml
> password: "S3kr1t"
> ```
> is equivalent to
> ```yaml
> auth:
> key-management: psk
> password: "S3kr1t"
> ```
- **mode** (scalar)
> Possible access point modes are `infrastructure` (the default),
> `ap` (create an access point to which other devices can connect),
> and `adhoc` (peer to peer networks without a central access point).
> `ap` is only supported with NetworkManager.
- **bssid** (scalar) – since **0.99**
> If specified, directs the device to only associate with the given
> access point.
- **band** (scalar) – since **0.99**
> Possible bands are `5GHz` (for 5GHz 802.11a) and `2.4GHz`
> (for 2.4GHz 802.11), do not restrict the 802.11 frequency band of the
> network if unset (the default).
- **channel** (scalar) – since **0.99**
> Wireless channel to use for the Wi-Fi connection. Because channel
> numbers overlap between bands, this property takes effect only if
> the `band` property is also set.
- **hidden** (bool) – since **0.100**
> Set to `true` to change the SSID scan technique for connecting to
> hidden WiFi networks. Note this may have slower performance compared
> to `false` (the default) when connecting to publicly broadcast
> SSIDs.
- **wakeonwlan** (sequence of scalars) – since **0.99**
> This enables WakeOnWLan on supported devices. Not all drivers support all
> options. May be any combination of `any`, `disconnect`, `magic_pkt`,
> `gtk_rekey_failure`, `eap_identity_req`, `four_way_handshake`,
> `rfkill_release` or `tcp` (NetworkManager only). Or the exclusive
> `default` flag (the default).
- **regulatory-domain** (scalar) – since **0.105**
> This can be used to define the radio's regulatory domain, to make use of
> additional WiFi channels outside the "world domain". Takes an ISO /
> IEC 3166 country code (like `GB`) or `00` to reset to the "world domain".
> See [wireless-regdb](https://git.kernel.org/pub/scm/linux/kernel/git/sforshee/wireless-regdb.git/tree/db.txt)
> for available values.
>
> **Requires dependency: iw**, if it is to be used outside the `networkd`
> (wpa_supplicant) backend.
## Properties for device type `bridges:`
**Status**: Optional.
**Purpose**: Use the `bridges` key to create Bridge interfaces.
**Structure**: The key consists of a mapping of Bridge interface names. Each
`bridge` has an optional list of interfaces that will be bridged together. The
interfaces listed in the `interfaces` key (`enp5s0` and `enp5s1` below) must
also be defined in your Netplan configuration. The general configuration
structure for Bridges is shown below.
```yaml
network:
bridges:
br0:
interfaces:
- enp5s0
- enp5s1
dhcp4: true
...
```
When applied, a virtual interface of type bridge called `br0` will be created in the system.
The specific settings for bridges are defined below.
- **interfaces** (sequence of scalars)
> All devices matching this ID list will be added to the bridge. This may
> be an empty list, in which case the bridge will be brought online with
> no member interfaces.
Example:
```yaml
network:
ethernets:
switchports:
match: {name: "enp2*"}
[...]
bridges:
br0:
interfaces: [switchports]
```
- **parameters** (mapping)
> Customization parameters for special bridging options. Time intervals
> may need to be expressed as a number of seconds or milliseconds: the
> default value type is specified below. If necessary, time intervals can
> be qualified using a time suffix (such as "s" for seconds, "ms" for
> milliseconds) to allow for more control over its behavior.
- **ageing-time**, **aging-time** (scalar)
> Set the period of time to keep a MAC address in the forwarding
> database after a packet is received. This maps to the AgeingTimeSec=
> property when the networkd renderer is used. If no time suffix is
> specified, the value will be interpreted as seconds.
- **priority** (scalar)
> Set the priority value for the bridge. This value should be a
> number between `0` and `65535`. Lower values mean higher
> priority. The bridge with the higher priority will be elected as
> the root bridge.
- **port-priority** (mapping)
> Set the port priority per interface. The priority value is
> a number between `0` and `63`. This metric is used in the
> designated port and root port selection algorithms.
Example:
```yaml
network:
ethernets:
eth0:
dhcp4: false
eth1:
dhcp4: false
bridges:
br0:
interfaces: [eth0, eth1]
parameters:
port-priority:
eth0: 10
eth1: 20
```
- **forward-delay** (scalar)
> Specify the period of time the bridge will remain in Listening and
> Learning states before getting to the Forwarding state. This field
> maps to the ForwardDelaySec= property for the networkd renderer.
> If no time suffix is specified, the value will be interpreted as
> seconds.
- **hello-time** (scalar)
> Specify the interval between two hello packets being sent out from
> the root and designated bridges. Hello packets communicate
> information about the network topology. When the networkd renderer
> is used, this maps to the HelloTimeSec= property. If no time suffix
> is specified, the value will be interpreted as seconds.
- **max-age** (scalar)
> Set the maximum age of a hello packet. If the last hello packet is
> older than that value, the bridge will attempt to become the root
> bridge. This maps to the MaxAgeSec= property when the networkd
> renderer is used. If no time suffix is specified, the value will be
> interpreted as seconds.
- **path-cost** (mapping)
> Set the per-interface cost of a path on the bridge. Faster interfaces
> should have a lower cost. This allows a finer control on the network
> topology so that the fastest paths are available whenever possible.
Example:
```yaml
network:
ethernets:
eth0:
dhcp4: false
eth1:
dhcp4: false
bridges:
br0:
interfaces: [eth0, eth1]
parameters:
path-cost:
eth0: 100
eth1: 200
```
- **stp** (bool)
> Define whether the bridge should use Spanning Tree Protocol. The
> default value is "true", which means that Spanning Tree should be
> used.
## Properties for device type `dummy-devices:`
**Status**: Optional.
**Purpose**: Use the `dummy-devices` key to create virtual interfaces.
**Structure**: The key consists of a mapping of interface names.
Dummy devices are virtual devices that can be used to route packets to
without actually transmitting them.
```yaml
network:
dummy-devices:
dm0:
addresses:
- 192.168.0.123/24
...
```
When applied, a virtual interface called `dm0` will be created in the system.
See the ["Properties for all device types"](#properties-for-all-device-types) section for the list of properties that can be
used with this type of interface.
## Properties for device type `bonds:`
**Status**: Optional.
**Purpose**: Use the `bonds` key to create Bond (Link Aggregation) interfaces.
**Structure**: The key consists of a mapping of Bond interface names. Each
`bond` has an optional list of interfaces that will be part of the aggregation.
The interfaces listed in the `interfaces` key must also be defined in your
Netplan configuration. The general configuration structure for Bonds is shown
below.
```yaml
network:
bonds:
bond0:
interfaces:
- enp5s0
- enp5s1
- enp5s2
mode: active-backup
...
```
When applied, a virtual interface of type bond called `bond0` will be created in the system.
The specific settings for bonds are defined below.
- **interfaces** (sequence of scalars)
> All devices matching this ID list will be added to the bond.
Example:
```yaml
network:
ethernets:
switchports:
match: {name: "enp2*"}
[...]
bonds:
bond0:
interfaces: [switchports]
```
- **parameters** (mapping)
> Customization parameters for special bonding options. Time intervals
> may need to be expressed as a number of seconds or milliseconds: the
> default value type is specified below. If necessary, time intervals can
> be qualified using a time suffix (such as "s" for seconds, "ms" for
> milliseconds) to allow for more control over its behavior.
- **mode** (scalar)
> Set the bonding mode used for the interfaces. The default is
> `balance-rr` (round robin). Possible values are `balance-rr`,
> `active-backup`, `balance-xor`, `broadcast`, `802.3ad`,
> `balance-tlb`, and `balance-alb`.
> For Open vSwitch `active-backup` and the additional modes
> `balance-tcp` and `balance-slb` are supported.
- **lacp-rate** (scalar)
> Set the rate at which LACPDUs are transmitted. This is only useful
> in 802.3ad mode. Possible values are `slow` (30 seconds, default),
> and `fast` (every second).
- **mii-monitor-interval** (scalar)
> Specifies the interval for MII monitoring (verifying if an interface
> of the bond has carrier). The default is `0`; which disables MII
> monitoring. This is equivalent to the MIIMonitorSec= field for the
> networkd backend. If no time suffix is specified, the value will be
> interpreted as milliseconds.
- **min-links** (scalar)
> The minimum number of links up in a bond to consider the bond
> interface to be up.
- **transmit-hash-policy** (scalar)
> Specifies the transmit hash policy for the selection of ports. This
> is only useful in balance-xor, 802.3ad and balance-tlb modes.
> Possible values are `layer2`, `layer3+4`, `layer2+3`,
> `encap2+3`, and `encap3+4`.
- **ad-select** (scalar)
> Set the aggregation selection mode. Possible values are `stable`,
> `bandwidth`, and `count`. This option is only used in 802.3ad
> mode.
- **all-members-active** (bool) – since **0.106**
> If the bond should drop duplicate frames received on inactive ports,
> set this option to `false`. If they should be delivered, set this
> option to `true`. The default value is false, and is the desirable
> behavior in most situations.
>
> Alias: **all-slaves-active**
- **arp-interval** (scalar)
> Set the interval value for how frequently ARP link monitoring should
> happen. The default value is `0`, which disables ARP monitoring.
> For the networkd backend, this maps to the ARPIntervalSec= property.
> If no time suffix is specified, the value will be interpreted as
> milliseconds.
- **arp-ip-targets** (sequence of scalars)
> IPs of other hosts on the link which should be sent ARP requests in
> order to validate that a port is up. This option is only used when
> `arp-interval` is set to a value other than `0`. At least one IP
> address must be given for ARP link monitoring to function. Only IPv4
> addresses are supported. You can specify up to 16 IP addresses. The
> default value is an empty list.
- **arp-validate** (scalar)
> Configure how ARP replies are to be validated when using ARP link
> monitoring. Possible values are `none`, `active`, `backup`,
> and `all`.
- **arp-all-targets** (scalar)
> Specify whether to use any ARP IP target being up as sufficient for
> a port to be considered up; or if all the targets must be up. This
> is only used for `active-backup` mode when `arp-validate` is
> enabled. Possible values are `any` and `all`.
- **up-delay** (scalar)
> Specify the delay before enabling a link once the link is physically
> up. The default value is `0`. This maps to the UpDelaySec= property
> for the networkd renderer. This option is only valid for the miimon
> link monitor. If no time suffix is specified, the value will be
> interpreted as milliseconds.
- **down-delay** (scalar)
> Specify the delay before disabling a link once the link has been
> lost. The default value is `0`. This maps to the DownDelaySec=
> property for the networkd renderer. This option is only valid for the
> miimon link monitor. If no time suffix is specified, the value will
> be interpreted as milliseconds.
- **fail-over-mac-policy** (scalar)
> Set whether to set all ports to the same MAC address when adding
> them to the bond, or how else the system should handle MAC addresses.
> The possible values are `none`, `active`, and `follow`.
- **gratuitous-arp** (scalar)
> Specify how many ARP packets to send after failover. Once a link is
> up on a new port, a notification is sent and possibly repeated if
> this value is set to a number greater than `1`. The default value
> is `1` and valid values are between `1` and `255`. This only
> affects `active-backup` mode.
>
> For historical reasons, the misspelling `gratuitious-arp` is also
> accepted and has the same function.
- **packets-per-member** (scalar) – since **0.106**
> In `balance-rr` mode, specifies the number of packets to transmit
> on a port before switching to the next. When this value is set to
> `0`, ports are chosen at random. Allowable values are between
> `0` and `65535`. The default value is `1`. This setting is
> only used in `balance-rr` mode.
>
> Alias: **packets-per-slave**
- **primary-reselect-policy** (scalar)
> Set the reselection policy for the primary port. On failure of the
> active port, the system will use this policy to decide how the new
> active port will be chosen and how recovery will be handled. The
> possible values are `always`, `better`, and `failure`.
- **resend-igmp** (scalar)
> In modes `balance-rr`, `active-backup`, `balance-tlb` and
> `balance-alb`, a failover can switch IGMP traffic from one
> port to another.
>
> This parameter specifies how many IGMP membership reports
> are issued on a failover event. Values range from 0 to 255. 0
> disables sending membership reports. Otherwise, the first
> membership report is sent on failover and subsequent reports
> are sent at 200ms intervals.
- **learn-packet-interval** (scalar)
> Specify the interval between sending learning packets to
> each port. The value range is between `1` and `0x7fffffff`.
> The default value is `1`. This option only affects `balance-tlb`
> and `balance-alb` modes. Using the networkd renderer, this field
> maps to the LearnPacketIntervalSec= property. If no time suffix is
> specified, the value will be interpreted as seconds.
- **primary** (scalar)
> Specify a device to be used as a primary port, or preferred device
> to use as a port for the bond (i.e. the preferred device to send
> data through), whenever it is available. This only affects
> `active-backup`, `balance-alb`, and `balance-tlb` modes.
## Properties for device type `tunnels:`
**Status**: Optional.
**Purpose**: Use the `tunnels` key to create virtual tunnel interfaces.
**Structure**: The key consists of a mapping of tunnel interface names. Each
`tunnel` requires the identification of the tunnel mode (see the section `mode`
below for the list of supported modes). The general configuration structure for
Tunnels is shown below.
```yaml
network:
tunnels:
tunnel0:
mode: SCALAR
...
```
When applied, a virtual interface called `tunnel0` will be created in the system. Its operation mode is defined by the property `mode`.
Tunnels allow traffic to pass as if it was between systems on the same local
network, although systems may be far from each other but reachable via the
Internet. They may be used to support IPv6 traffic on a network where the ISP
does not provide the service, or to extend and "connect" separate local
networks. Please see for
more general information about tunnels.
The specific settings for tunnels are defined below.
- **mode** (scalar)
> Defines the tunnel mode. Valid options are `sit`, `gre`, `ip6gre`,
> `ipip`, `ipip6`, `ip6ip6`, `vti`, `vti6`, `wireguard`, `vxlan`,
> `gretap` and `ip6gretap` modes.
> In addition, the `NetworkManager` backend supports `isatap` tunnels.
- **local** (scalar)
> Defines the address of the local endpoint of the tunnel. (For VXLAN) This
> should match one of the parent's IP addresses or make use of the networkd
> special values.
- **remote** (scalar)
> Defines the address of the remote endpoint of the tunnel or multicast group
> IP address for VXLAN.
- **ttl** (scalar) – since **0.103**
> Defines the Time To Live (TTL) of the tunnel.
> Takes a number in the range `1..255`.
- **key** (scalar or mapping)
> Define keys to use for the tunnel. The key can be a number or a dotted
> quad (an IPv4 address). For `wireguard` it can be a base64-encoded
> private key or (as of `networkd` v242+) an absolute path to a file,
> containing the private key (since 0.100).
> It is used for identification of IP transforms. This is only required
> for `vti` and `vti6` when using the networkd backend.
>
> This field may be used as a scalar (meaning that a single key is
> specified and to be used for input, output and private key), or as a
> mapping, where you can further specify `input`/`output`/`private`.
- **input** (scalar)
> The input key for the tunnel
- **output** (scalar)
> The output key for the tunnel
- **private** (scalar) – since **0.100**
> A base64-encoded private key required for WireGuard tunnels. When the
> `systemd-networkd` backend (v242+) is used, this can also be an
> absolute path to a file containing the private key.
- **private-key-flags** (sequence of scalars) – since **0.107**
> Private key flags used by Network Manager. Possible values are:
> `agent-owned`, `not-saved` and `not-required`.
>
> `agent-owned`: a user-session secret agent is responsible for
> providing and storing this secret.
>
> `not-saved`: this secret should not be saved but should be
> requested from the user each time it is required.
>
> `not-required`: this flag hints that the secret is not required
> and should not be requested from the user.
Example:
```yaml
network:
renderer: NetworkManager
tunnels:
wg0:
mode: wireguard
port: 5182
key:
private-key-flags:
- agent-owned
peers:
- keys:
public: rlbInAj0qV69CysWPQY7KEBnKxpYCpaWqOs/dLevdWc=
allowed-ips: [0.0.0.0/0, "2001:fe:ad:de:ad:be:ef:1/24"]
keepalive: 23
endpoint: 1.2.3.4:5
```
- **keys** (scalar or mapping)
> Alternate name for the `key` field. See above.
Examples:
```yaml
network:
tunnels:
tun0:
mode: gre
local: ...
remote: ...
keys:
input: 1234
output: 5678
```
```yaml
network:
tunnels:
tun0:
mode: vti6
local: ...
remote: ...
key: 59568549
```
```yaml
network:
tunnels:
wg0:
mode: wireguard
addresses: [...]
peers:
- keys:
public: rlbInAj0qV69CysWPQY7KEBnKxpYCpaWqOs/dLevdWc=
shared: /path/to/shared.key
...
key: mNb7OIIXTdgW4khM7OFlzJ+UPs7lmcWHV7xjPgakMkQ=
```
```yaml
network:
tunnels:
wg0:
mode: wireguard
addresses: [...]
peers:
- keys:
public: rlbInAj0qV69CysWPQY7KEBnKxpYCpaWqOs/dLevdWc=
...
keys:
private: /path/to/priv.key
```
WireGuard specific keys:
- **mark** (scalar) – since **0.100**
> Firewall mark for outgoing WireGuard packets from this interface,
> optional.
- **port** (scalar) – since **0.100**
> UDP port to listen at or `auto`. Optional, defaults to `auto`.
- **peers** (sequence of mappings) – since **0.100**
> A list of peers, each having keys documented below.
Example:
```yaml
network:
tunnels:
wg0:
mode: wireguard
key: /path/to/private.key
mark: 42
port: 5182
peers:
- keys:
public: rlbInAj0qV69CysWPQY7KEBnKxpYCpaWqOs/dLevdWc=
allowed-ips: [0.0.0.0/0, "2001:fe:ad:de:ad:be:ef:1/24"]
keepalive: 23
endpoint: 1.2.3.4:5
- keys:
public: M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG4=
shared: /some/shared.key
allowed-ips: [10.10.10.20/24]
keepalive: 22
endpoint: 5.4.3.2:1
```
- **endpoint** (scalar) – since **0.100**
> Remote endpoint IPv4/IPv6 address or a hostname, followed by a colon
> and a port number.
- **allowed-ips** (sequence of scalars) – since **0.100**
> A list of IP (v4 or v6) addresses with CIDR masks from which this peer
> is allowed to send incoming traffic and to which outgoing traffic for
> this peer is directed. The catch-all 0.0.0.0/0 may be specified for
> matching all IPv4 addresses, and ::/0 may be specified for matching
> all IPv6 addresses.
- **keepalive** (scalar) – since **0.100**
> An interval in seconds, between 1 and 65535 inclusive, of how often to
> send an authenticated empty packet to the peer for the purpose of
> keeping a stateful firewall or NAT mapping valid persistently. Optional.
- **keys** (mapping) – since **0.100**
> Define keys to use for the WireGuard peers.
>
> This field can be used as a mapping, where you can further specify the
> `public` and `shared` keys.
- **public** (scalar) – since **0.100**
> A base64-encoded public key, required for WireGuard peers.
- **shared** (scalar) – since **0.100**
> A base64-encoded preshared key. Optional for WireGuard peers.
> When the `systemd-networkd` backend (v242+) is used, this can
> also be an absolute path to a file containing the preshared key.
VXLAN specific keys:
- **id** (scalar) – since **0.105**
> The VXLAN Network Identifier (VNI or VXLAN Segment ID).
> Takes a number in the range `1..16777215`.
- **link** (scalar) – since **0.105**
> netplan ID of the parent device definition to which this VXLAN gets
> connected.
- **type-of-service** (scalar) – since **0.105**
> The Type Of Service byte value for a vxlan interface.
- **mac-learning** (scalar) – since **0.105**
> Takes a boolean. When `true`, enables dynamic MAC learning to discover
> remote MAC addresses.
- **ageing**, **aging** (scalar) – since **0.105**
> The lifetime of Forwarding Database entry learned by the kernel, in
> seconds.
- **limit** (scalar) – since **0.105**
> Configures maximum number of FDB entries.
- **arp-proxy** (scalar) – since **0.105**
> Takes a boolean. When `true`, bridge-connected VXLAN tunnel endpoint
> answers ARP requests from the local bridge on behalf of remote Distributed
> Overlay Virtual Ethernet (DOVE) clients. Defaults to `false`.
- **notifications** (sequence of scalars) – since **0.105**
> Takes the flags `l2-miss` and `l3-miss` to enable netlink LLADDR and/or
> netlink IP address miss notifications.
- **short-circuit** (scalar) – since **0.105**
> Takes a boolean. When `true`, route short circuiting is turned on.
- **checksums** (sequence of scalars) – since **0.105**
> Takes the flags `udp`, `zero-udp6-tx`, `zero-udp6-rx`, `remote-tx` and
> `remote-rx` to enable transmitting UDP checksums in VXLAN/IPv4,
> send/receive zero checksums in VXLAN/IPv6 and enable sending/receiving
> checksum offloading in VXLAN.
- **extensions** (sequence of scalars) – since **0.105**
> Takes the flags `group-policy` and `generic-protocol` to enable the "Group
> Policy" and/or "Generic Protocol" VXLAN extensions.
- **port** (scalar) – since **0.105**
> Configures the default destination UDP port. If the destination port is
> not specified then Linux kernel default will be used. Set to `4789` to get
> the IANA assigned value.
- **port-range** (sequence of scalars) – since **0.105**
> Configures the source port range for the VXLAN. The kernel assigns the
> source UDP port based on the flow to help the receiver to do load
> balancing. When this option is not set, the normal range of local UDP
> ports is used. Uses the form `[LOWER, UPPER]`.
- **flow-label** (scalar) – since **0.105**
> Specifies the flow label to use in outgoing packets. The valid range
> is `0-1048575`.
- **do-not-fragment** (scalar) – since **0.105**
> Allows setting the IPv4 Do not Fragment (DF) bit in outgoing packets.
> Takes a boolean value. When unset, the kernel's default will be used.
## Properties for device type `virtual-ethernets:`
**Status**: Optional.
**Purpose**: Use the `virtual-ethernets` key to create virtual Ethernet interfaces.
**Structure**: The key consists of a mapping of virtual-ethernet interface names. Each
`virtual-ethernet` requires a `peer`. In order to have a fully working `virtual-ethernet` pair,
both devices must be defined, i.e., only setting the `peer` key with the peer
name is not enough, the peer interface must also be defined and set the first one
as its peer.
The general configuration structure for Virtual Ethernets is shown below.
```yaml
network:
virtual-ethernets:
veth0:
peer: veth1
veth1:
peer: veth0
```
When applied, two virtual interfaces called `veth0` and `veth1` will be created in the system.
Virtual Ethernets acts as tunnels forwarding traffic from one interface to the other.
They can be used to connect two separate virtual networks such as network namespaces and
bridges. It's not possible to move `virtual-ethernets` to different namespaces through Netplan at the
present moment.
The specific settings for virtual-ethernets are defined below.
- **peer** (scalar)
> Defines the virtual-ethernet peer. The peer interface must also be a virtual-ethernet device.
Below is a complete example that uses a pair of virtual Ethernet devices to create a link between two
bridges:
```yaml
network:
version: 2
renderer: networkd
virtual-ethernets:
veth0-peer1:
peer: veth0-peer2
veth0-peer2:
peer: veth0-peer1
bridges:
br0:
interfaces:
- veth0-peer1
br1:
interfaces:
- veth0-peer2
```
## Properties for device type `vlans:`
**Status**: Optional.
**Purpose**: Use the `vlans` key to create VLAN interfaces.
**Structure**: The key consists of a mapping of VLAN interface names. The
interface used in the `link` option (`enp5s0` in the example below) must also be
defined in the Netplan configuration. The general configuration structure for
Vlans is shown below.
```yaml
network:
vlans:
vlan123:
id: 123
link: enp5s0
dhcp4: yes
```
The specific settings for VLANs are defined below.
- **id** (scalar)
> VLAN ID, a number between `0` and `4094`.
- **link** (scalar)
> netplan ID of the underlying device definition on which this VLAN gets
> created.
Example:
```yaml
network:
ethernets:
eno1: {...}
vlans:
en-intra:
id: 1
link: eno1
dhcp4: yes
en-vpn:
id: 2
link: eno1
addresses: [...]
```
## Properties for device type `vrfs:`
**Status**: Optional.
**Purpose**: Use the `vrfs` key to create Virtual Routing and Forwarding (VRF)
interfaces.
**Structure**: The key consists of a mapping of VRF interface names. The
interface used in the `link` option (`enp5s0` in the example below) must also be
defined in the Netplan configuration. The general configuration structure for
VRFs is shown below.
```yaml
network:
renderer: networkd
vrfs:
vrf1:
table: 1
interfaces:
- enp5s0
routes:
- to: default
via: 10.10.10.4
routing-policy:
- from: 10.10.10.42
```
- **table** (scalar) – since **0.105**
> The numeric routing table identifier. This setting is compulsory.
- **interfaces** (sequence of scalars) – since **0.105**
> All devices matching this ID list will be added to the VRF. This may
> be an empty list, in which case the VRF will be brought online with
> no member interfaces.
- **routes** (sequence of mappings) – since **0.105**
> Configure static routing for the device; see the `Routing` section.
> The `table` value is implicitly set to the VRF's `table`.
- **routing-policy** (sequence of mappings) – since **0.105**
> Configure policy routing for the device; see the ``Routing`` section.
> The `table` value is implicitly set to the VRF's `table`.
Example:
```yaml
network:
vrfs:
vrf20:
table: 20
interfaces: [ br0 ]
routes:
- to: default
via: 10.10.10.3
routing-policy:
- from: 10.10.10.42
[...]
bridges:
br0:
interfaces: []
```
## Properties for device type `nm-devices:`
**Status**: Optional. Its use is not recommended.
**Purpose**: Use the `nm-devices` key to configure device types that are not
supported by Netplan. This is NetworkManager specific configuration.
**Structure**: The key consists of a mapping of NetworkManager connections. The
`nm-devices` device type is for internal use only and should not be used in
normal configuration files. It enables a fallback mode for unsupported settings,
using the `passthrough` mapping. The general configuration structure for NM
connections is shown below.
```yaml
network:
version: 2
nm-devices:
NM-db5f0f67-1f4c-4d59-8ab8-3d278389cf87:
renderer: NetworkManager
networkmanager:
uuid: "db5f0f67-1f4c-4d59-8ab8-3d278389cf87"
name: "myvpnconnection"
passthrough:
connection.type: "vpn"
vpn.ca: "path to ca.crt"
vpn.cert: "path to client.crt"
vpn.cipher: "AES-256-GCM"
vpn.connection-type: "tls"
vpn.dev: "tun"
vpn.key: "path to client.key"
vpn.remote: "1.2.3.4:1194"
vpn.service-type: "org.freedesktop.NetworkManager.openvpn"
```
## Backend-specific configuration parameters
In addition to the other fields available to configure interfaces, some
backends may require to record some of their own parameters in netplan,
especially if the netplan definitions are generated automatically by the
consumer of that backend. Currently, this is only used with `NetworkManager`.
- **networkmanager** (mapping) – since **0.99**
> Keeps the NetworkManager-specific configuration parameters used by the
> daemon to recognize connections.
- **name** (scalar) – since **0.99**
> Set the display name for the connection.
- **uuid** (scalar) – since **0.99**
> Defines the UUID (unique identifier) for this connection, as
> generated by NetworkManager itself.
- **stable-id** (scalar) – since **0.99**
> Defines the stable ID (a different form of a connection name) used
> by NetworkManager in case the name of the connection might otherwise
> change, such as when sharing connections between users.
- **device** (scalar) – since **0.99**
> Defines the interface name for which this connection applies.
- **passthrough** (mapping) – since **0.102**
> Can be used as a fallback mechanism to missing keyfile settings.
netplan-0.107.1/doc/netplan.md 0000777 0000000 0000000 00000000000 14536110317 0020770 2netplan-yaml.md ustar 00root root 0000000 0000000 netplan-0.107.1/doc/netplan.svg 0000664 0000000 0000000 00000006104 14536110317 0016252 0 ustar 00root root 0000000 0000000
image/svg+xml
netplan-0.107.1/doc/nm-all.md 0000664 0000000 0000000 00000002044 14536110317 0015571 0 ustar 00root root 0000000 0000000 ## NetworkManager default configuration
Without configuration, Netplan will not do anything. Therefore, on Desktop
systems, a useful configuration snippet to just bring up networking via DHCP is
as follows:
```yaml
network:
version: 2
renderer: NetworkManager
```
This will make NetworkManager manage all devices and by default. Any ethernet
device will come up with DHCP, once carrier is detected. This is basically
Netplan passing control over to NetworkManager at boot time.
You can still define any more specific IDs in you Netplan configuration, to
configure interfaces individually, according to Netplan's [YAML reference](/netplan-yaml/).
When NetworkManager's [Netplan desktop integration}(/netplan-everywhere/) is
activated, NetworkManager will automatically create specific Netplan IDs for
each of its connection profiles.
This configuration snippet is shipped by default on Ubuntu Desktop systems
through the [ubuntu-settings](https://launchpad.net/ubuntu/+source/ubuntu-settings)
package as `/usr/lib/netplan/00-network-manager-all.yaml`. netplan-0.107.1/doc/reference.md 0000664 0000000 0000000 00000003336 14536110317 0016354 0 ustar 00root root 0000000 0000000 # Reference
## YAML configuration
Netplan's configuration files use the
[YAML (v1.1)]() format. All files in
`/{lib,etc,run}/netplan/*.yaml` are considered and are supposed to use
restrictive file permissions (`600` / `rw-------`), i.e. owner (root) read-write
only.
The top-level node in a netplan configuration file is a ``network:`` mapping
that contains ``version: 2`` (the YAML currently being used by curtin, MaaS,
etc. is version 1), and then device definitions grouped by their type, such as
``ethernets:``, ``modems:``, ``wifis:``, or ``bridges:``. These are the types
that our renderer can understand and are supported by our backends.
```{toctree}
---
maxdepth: 1
---
netplan-yaml
```
## libnetplan API
`libnetplan` is a component of the Netplan project that contains the logic for
data parsing, validation and generation. It is build as a dynamic `.so` library
that can be used from different binaries (like Netplan’s `generate`,
`netplan-dbus`, the `netplan apply/try/get/set/...` CLI or via the corresponding
Python bindings or external applications like the NetworkManager, using the
Netplan backend).
```{toctree}
libnetplan API
```
## Netplan CLI
Netplan's manpages describe the usage of the different command line interface
tools available. Those are also installed on a system running Netplan and can be
accessed, using the `man` utility.
```{toctree}
---
maxdepth: 2
---
cli
```
## Netplan D-Bus
Netplan provides a daemon that can be run to provide the `io.netplan.Netplan`
D-Bus API, to control certain aspects of a system's Netplan configuration
programmatically. See also: [DBus config API](/dbus-config).
```{toctree}
---
maxdepth: 1
---
Netplan D-Bus
```
netplan-0.107.1/doc/requirements.txt 0000664 0000000 0000000 00000000162 14536110317 0017352 0 ustar 00root root 0000000 0000000 sphinx
furo
sphinx-design
sphinxcontrib-spelling
sphinx-autobuild
pyenchant
myst-parser
sphinx-copybutton
breathe
netplan-0.107.1/doc/spelling_wordlist.txt 0000664 0000000 0000000 00000001621 14536110317 0020374 0 ustar 00root root 0000000 0000000 abc
adhoc
anycast
backend
Backend
backends
blackhole
bootup
bugtracker
cancelled
checksumming
checksums
config
ConnectX
curtin
dbus
DBus
decrypt
dev
dhcp
dir
distil
dmesg
empathic
engreen
enp
enred
ethernet
ethernets
Ethernets
failover
favourable
Fi
honoured
hostname
howto
howtos
https
ifnames
init
initramfs
instantiation
io
IoT
ip
ipip
iptables
ipv
IPv
jeopardise
json
keyfile
Lexicographically
Libera
libnetplan
libvirt
libvirtd
loopback
lxd
MaaS
manpages
Mellanox
miimon
minimises
multicast
nameservers
namespaces
nat
natively
netlink
netplan
networkd
networkmanager
NetworkManager
nmconnection
openvswitch
ovs
passthrough
pre
prefixtlb
preshared
programmatically
ra
recognise
renderer
reselection
rulebook
SSIDs
stateful
statelessly
subnet
subtree
systemd
tcp
timesyncd
tlb
tunnelled
tx
udev
unencrypted
unicast
untagged
veth
vlan
Vlans
vrf
vsctl
vSwitch
vxlan
Wi
wifi
wifis
wpa
wpasupplicant
xresolve
yaml
netplan-0.107.1/doc/structure-id.md 0000664 0000000 0000000 00000010026 14536110317 0017042 0 ustar 00root root 0000000 0000000 ## Introduction
Distribution installers, cloud instantiation, image builds for particular
devices, or any other way to deploy an operating system put its desired
network configuration into YAML configuration file(s). During
early boot, the netplan "network renderer" runs which reads
`/{lib,etc,run}/netplan/*.yaml` and writes configuration to `/run` to hand
off control of devices to the specified networking daemon.
- Configured devices get handled by systemd-networkd by default,
unless explicitly marked as managed by a specific renderer (NetworkManager)
- Devices not covered by the network config do not get touched at all.
- Usable in initramfs (few dependencies and fast)
- No persistent generated config, only original YAML config
- Parser supports multiple config files to allow applications like libvirt or
lxd to package up expected network config (`virbr0`, `lxdbr0`), or to change
the global default policy to use NetworkManager for everything.
- Retains the flexibility to change backends/policy later or adjust to
removing NetworkManager, as generated configuration is ephemeral.
## General structure
netplan's configuration files use the
[YAML](http://yaml.org/spec/1.1/current.html) format. All
`/{lib,etc,run}/netplan/*.yaml` are considered. Lexicographically later files
(regardless of in which directory they are) amend (new mapping keys) or
override (same mapping keys) previous ones. A file in `/run/netplan`
completely shadows a file with same name in `/etc/netplan`, and a file in
either of those directories shadows a file with the same name in `/lib/netplan`.
The top-level node in a netplan configuration file is a `network:` mapping
that contains `version: 2` (the YAML currently being used by curtin, MaaS,
etc. is version 1), and then device definitions grouped by their type, such as
`ethernets:`, `modems:`, `wifis:`, or `bridges:`. These are the types that our
renderer can understand and are supported by our backends.
Each type block contains device definitions as a map where the keys (called
"configuration IDs") are defined as below.
## Device configuration IDs
The key names below the per-device-type definition maps (like `ethernets:`)
are called "ID"s. They must be unique throughout the entire set of
configuration files. Their primary purpose is to serve as anchor names for
composite devices, for example to enumerate the members of a bridge that is
currently being defined.
(Since 0.97) If an interface is defined with an ID in a configuration file; it
will be brought up by the applicable renderer. To not have netplan touch an
interface at all, it should be completely omitted from the netplan configuration
files.
There are two physically/structurally different classes of device definitions,
and the ID field has a different interpretation for each:
Physical devices
> (Examples: ethernet, modem, wifi) These can dynamically come and go between
> reboots and even during runtime (hot plugging). In the generic case, they
> can be selected by `match:` rules on desired properties, such as name/name
> pattern, MAC address, driver, or device paths. In general these will match
> any number of devices (unless they refer to properties which are unique
> such as the full path or MAC address), so without further knowledge about
> the hardware these will always be considered as a group.
>
> It is valid to specify no match rules at all, in which case the ID field is
> simply the interface name to be matched. This is mostly useful if you want
> to keep simple cases simple, and it's how network device configuration has
> been done for a long time.
>
> If there are ``match``: rules, then the ID field is a purely opaque name
> which is only being used for references from definitions of compound
> devices in the config.
Virtual devices
> (Examples: veth, bridge, bond, vrf) These are fully under the control of the
> config file(s) and the network stack. I. e. these devices are being created
> instead of matched. Thus `match:` and `set-name:` are not applicable for
> these, and the ID field is the name of the created virtual device.
netplan-0.107.1/doc/tutorial.md 0000664 0000000 0000000 00000000056 14536110317 0016255 0 ustar 00root root 0000000 0000000 # Tutorial
```{toctree}
netplan-tutorial
```
netplan-0.107.1/examples/ 0000775 0000000 0000000 00000000000 14536110317 0015140 5 ustar 00root root 0000000 0000000 netplan-0.107.1/examples/bonding.yaml 0000664 0000000 0000000 00000000427 14536110317 0017447 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
enp3s0: {}
enp4s0: {}
bonds:
bond0:
dhcp4: yes
interfaces:
- enp3s0
- enp4s0
parameters:
mode: active-backup
primary: enp3s0
mii-monitor-interval: 100
netplan-0.107.1/examples/bonding_router.yaml 0000664 0000000 0000000 00000001715 14536110317 0021050 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
enp1s0:
dhcp4: no
enp2s0:
dhcp4: no
enp3s0:
dhcp4: no
optional: true
enp4s0:
dhcp4: no
optional: true
enp5s0:
dhcp4: no
optional: true
enp6s0:
dhcp4: no
optional: true
bonds:
bond-lan:
interfaces: [enp2s0, enp3s0]
addresses: [192.168.93.2/24]
parameters:
mode: 802.3ad
mii-monitor-interval: 1
bond-wan:
interfaces: [enp1s0, enp4s0]
addresses: [192.168.1.252/24]
nameservers:
search: [local]
addresses: [8.8.8.8, 8.8.4.4]
parameters:
mode: active-backup
mii-monitor-interval: 1
gratuitious-arp: 5
routes:
- to: default
via: 192.168.1.1
bond-conntrack:
interfaces: [enp5s0, enp6s0]
addresses: [192.168.254.2/24]
parameters:
mode: balance-rr
mii-monitor-interval: 1
netplan-0.107.1/examples/bridge.yaml 0000664 0000000 0000000 00000000234 14536110317 0017257 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
enp3s0:
dhcp4: no
bridges:
br0:
dhcp4: yes
interfaces:
- enp3s0
netplan-0.107.1/examples/bridge_vlan.yaml 0000664 0000000 0000000 00000000365 14536110317 0020304 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
enp0s25:
dhcp4: true
bridges:
br0:
addresses: [ 10.3.99.25/24 ]
interfaces: [ vlan15 ]
vlans:
vlan15:
accept-ra: no
id: 15
link: enp0s25
netplan-0.107.1/examples/cffi-bindings.py 0000664 0000000 0000000 00000005671 14536110317 0020225 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3
# Copyright (C) 2023 Canonical, Ltd.
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
import io
import tempfile
from netplan import Parser, State, _create_yaml_patch
from netplan import NetplanException, NetplanParserException
FALLBACK_FILENAME = '70-netplan-set.yaml'
# This script is a demo/example, making use of Netplan's CFFI Python bindings.
# It does process your local /etc/netplan/ hierarchy, so be careful using it.
# At first, it creates a Parser() object and a YAML patch, setting a
# "network.ethernets.eth99.dhcp4=true" value. It loads any existing Netplan
# YAML hierarcy from /etc/netplan/ and loads/applies the above mentioned patch
# on top of it. Afterwards, it creates a State() object, importing parsed data
# for validation and checks for any errors.
# On succesful validation, it walks through all NetDefs in the validated
# Netplan state and prints the Netplan ID and backend renderer of given NetDef.
# Finally, it writes the validated state (including the eth99.dhcp4 setting)
# back to disk in /etc/netplan/.
if __name__ == '__main__':
yaml_path = ['network', 'ethernets', 'eth99', 'dhcp4']
value = 'true'
parser = Parser()
with tempfile.TemporaryFile() as tmp:
_create_yaml_patch(yaml_path, value, tmp)
tmp.flush()
# Parse the full, existing YAML config hierarchy
parser.load_yaml_hierarchy(rootdir='/')
# Load YAML patch, containing our new settings
tmp.seek(0, io.SEEK_SET)
parser.load_yaml(tmp)
# Validate the final parser state
state = State()
try:
# validation of current state + new settings
state.import_parser_results(parser)
except NetplanParserException as e:
print('Error in', e.filename, 'Row/Col', e.line, e.column, '->', e.message)
except NetplanException as e:
print('Error:', e.message)
# Walk through all NetdefIDs in the state and print their backend
# renderer, to demonstrate working with NetDefinitionIterator &
# NetDefinition
for netdef_id, netdef in state.netdefs.items():
print('Netdef', netdef_id, 'is managed by:', netdef.backend)
# Write the new data from the YAML patch to disk, updating an
# existing Netdef, if file already exists, or FALLBACK_FILENAME
state._update_yaml_hierarchy(FALLBACK_FILENAME, rootdir='/')
netplan-0.107.1/examples/dhcp.yaml 0000664 0000000 0000000 00000000126 14536110317 0016741 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
enp3s0:
dhcp4: true
netplan-0.107.1/examples/dhcp_wired8021x.yaml 0000664 0000000 0000000 00000000330 14536110317 0020633 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
enp3s0:
dhcp4: true
auth:
key-management: 802.1x
method: ttls
identity: fluffy@cisco.com
password: hash:83...11
netplan-0.107.1/examples/direct_connect_gateway.yaml 0000664 0000000 0000000 00000000274 14536110317 0022533 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
eth0:
addresses: [ "10.10.10.1/24" ]
routes:
- to: 0.0.0.0/0
via: 9.9.9.9
on-link: true
netplan-0.107.1/examples/direct_connect_gateway_ipv6.yaml 0000664 0000000 0000000 00000000331 14536110317 0023471 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
eth0:
addresses: [ "2001:cafe:face:beef::dead:dead/64" ]
routes:
- to: "::/0"
via: "2001:cafe:face::1"
on-link: true
netplan-0.107.1/examples/dummy-devices.yaml 0000664 0000000 0000000 00000000313 14536110317 0020574 0 ustar 00root root 0000000 0000000 network:
renderer: networkd
version: 2
dummy-devices:
dm0:
addresses:
- 10.1.2.3/24
dm1:
addresses:
- 192.168.1.2/28
- 2001:cafe:face:beef::dead:dead/64
netplan-0.107.1/examples/infiniband.yaml 0000664 0000000 0000000 00000000325 14536110317 0020125 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
ib0:
match:
macaddress: "11:22:33:44:55:66:77:88:99:00:11:22:33:44:55:66:77:88:99:00"
dhcp4: true
infiniband-mode: "connected"
netplan-0.107.1/examples/ipv6_tunnel.yaml 0000664 0000000 0000000 00000000575 14536110317 0020304 0 ustar 00root root 0000000 0000000 network:
version: 2
ethernets:
eth0:
addresses:
- 1.1.1.1/24
- "2001:cafe:face::1/64"
routes:
- to: default
via: 1.1.1.254
tunnels:
he-ipv6:
mode: sit
remote: 2.2.2.2
local: 1.1.1.1
addresses:
- "2001:dead:beef::2/64"
routes:
- to: default
via: "2001:dead:beef::1"
netplan-0.107.1/examples/loopback_interface.yaml 0000664 0000000 0000000 00000000176 14536110317 0021642 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
lo:
match:
name: lo
addresses: [ 7.7.7.7/32 ]
netplan-0.107.1/examples/meson.build 0000664 0000000 0000000 00000000422 14536110317 0017300 0 ustar 00root root 0000000 0000000 #https://mesonbuild.com/FAQ.html#but-i-really-want-to-use-wildcards
c = run_command(find, '-name', '*.yaml', check: true)
examples = c.stdout().strip().split('\n')
install_data(
examples,
install_dir: join_paths(get_option('datadir'), 'doc', 'netplan', 'examples'))
netplan-0.107.1/examples/modem.yaml 0000664 0000000 0000000 00000000550 14536110317 0017125 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: NetworkManager
modems:
cdc-wdm1:
mtu: 1600
apn: ISP.CINGULAR
username: ISP@CINGULARGPRS.COM
password: CINGULAR1
number: "*99#"
network-id: 24005
device-id: da812de91eec16620b06cd0ca5cbc7ea25245222
pin: 2345
sim-id: 89148000000060671234
sim-operator-id: 310260
netplan-0.107.1/examples/network_manager.yaml 0000664 0000000 0000000 00000000061 14536110317 0021204 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: NetworkManager
netplan-0.107.1/examples/offload.yaml 0000664 0000000 0000000 00000000463 14536110317 0017441 0 ustar 00root root 0000000 0000000 network:
version: 2
ethernets:
ens1:
receive-checksum-offload: false
transmit-checksum-offload: true
tcp-segmentation-offload: true
tcp6-segmentation-offload: true
generic-segmentation-offload: true
generic-receive-offload: true
large-receive-offload: true
netplan-0.107.1/examples/openvswitch.yaml 0000664 0000000 0000000 00000002127 14536110317 0020377 0 ustar 00root root 0000000 0000000 network:
version: 2
openvswitch:
protocols: [OpenFlow13, OpenFlow14, OpenFlow15]
ports:
- [patch0-1, patch1-0]
ssl:
ca-cert: /some/ca-cert.pem
certificate: /another/cert.pem
private-key: /private/key.pem
external-ids:
somekey: somevalue
other-config:
key: value
ethernets:
eth0:
addresses: [10.5.32.26/20]
openvswitch:
external-ids:
iface-id: mylocaliface
other-config:
disable-in-band: false
eth1: {}
bonds:
bond0:
interfaces: [patch1-0, eth1]
openvswitch:
lacp: passive
parameters:
mode: balance-tcp
bridges:
ovs0:
addresses: [10.5.48.11/20]
interfaces: [patch0-1, eth0, bond0]
openvswitch:
protocols: [OpenFlow10, OpenFlow11, OpenFlow12]
controller:
addresses: [unix:/var/run/openvswitch/ovs0.mgmt]
connection-mode: out-of-band
fail-mode: secure
mcast-snooping: true
external-ids:
iface-id: myhostname
other-config:
disable-in-band: true
netplan-0.107.1/examples/route_metric.yaml 0000664 0000000 0000000 00000000277 14536110317 0020533 0 ustar 00root root 0000000 0000000 network:
version: 2
ethernets:
enred:
dhcp4: yes
dhcp4-overrides:
route-metric: 100
engreen:
dhcp4: yes
dhcp4-overrides:
route-metric: 200
netplan-0.107.1/examples/source_routing.yaml 0000664 0000000 0000000 00000001061 14536110317 0021071 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
ens3:
addresses:
- 192.168.3.30/24
dhcp4: no
routes:
- to: 192.168.3.0/24
via: 192.168.3.1
table: 101
routing-policy:
- from: 192.168.3.0/24
table: 101
ens5:
addresses:
- 192.168.5.24/24
dhcp4: no
routes:
- to: default
via: 192.168.5.1
- to: 192.168.5.0/24
via: 192.168.5.1
table: 102
routing-policy:
- from: 192.168.5.0/24
table: 102
netplan-0.107.1/examples/sriov.yaml 0000664 0000000 0000000 00000000453 14536110317 0017170 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
eno1:
mtu: 9000
embedded-switch-mode: "switchdev"
enp1s16f1:
link: eno1
addresses : [ "10.15.98.25/24" ]
vf1:
match:
name: enp1s16f[2-3]
link: eno1
addresses : [ "10.15.99.25/24" ]
netplan-0.107.1/examples/sriov_vlan.yaml 0000664 0000000 0000000 00000000473 14536110317 0020212 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
eno1:
mtu: 9000
enp1s16f1:
link: eno1
addresses : [ "10.15.98.25/24" ]
vlans:
vlan1:
id: 15
link: enp1s16f1
addresses: [ "10.3.99.5/24" ]
vlan2_hw:
id: 10
link: enp1s16f1
renderer: sriov
netplan-0.107.1/examples/static.yaml 0000664 0000000 0000000 00000000420 14536110317 0017307 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
enp3s0:
addresses:
- 10.10.10.2/24
nameservers:
search: [mydomain, otherdomain]
addresses: [10.10.10.1, 1.1.1.1]
routes:
- to: default
via: 10.10.10.1
netplan-0.107.1/examples/static_multiaddress.yaml 0000664 0000000 0000000 00000000277 14536110317 0022101 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
enp3s0:
addresses:
- 10.100.1.38/24
- 10.100.1.39/24
routes:
- to: default
via: 10.100.1.1
netplan-0.107.1/examples/static_singlenic_multiip_multigateway.yaml 0000664 0000000 0000000 00000000531 14536110317 0025704 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
eno1:
addresses:
- 10.0.0.10/24
- 11.0.0.11/24
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4
routes:
- to: 0.0.0.0/0
via: 10.0.0.1
metric: 100
- to: 0.0.0.0/0
via: 11.0.0.1
metric: 200
netplan-0.107.1/examples/virtual-ethernet.yaml 0000664 0000000 0000000 00000000400 14536110317 0021320 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
virtual-ethernets:
veth0-peer1:
peer: veth0-peer2
veth0-peer2:
peer: veth0-peer1
bridges:
br0:
interfaces:
- veth0-peer1
br1:
interfaces:
- veth0-peer2
netplan-0.107.1/examples/vlan.yaml 0000664 0000000 0000000 00000001152 14536110317 0016763 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
ethernets:
mainif:
match:
macaddress: "de:ad:be:ef:ca:fe"
set-name: mainif
addresses: [ "10.3.0.5/23" ]
nameservers:
addresses: [ "8.8.8.8", "8.8.4.4" ]
search: [ example.com ]
routes:
- to: default
via: 10.3.0.1
vlans:
vlan15:
id: 15
link: mainif
addresses: [ "10.3.99.5/24" ]
vlan10:
id: 10
link: mainif
addresses: [ "10.3.98.5/24" ]
nameservers:
addresses: [ "127.0.0.1" ]
search: [ domain1.example.com, domain2.example.com ]
netplan-0.107.1/examples/vrf.yaml 0000664 0000000 0000000 00000000460 14536110317 0016621 0 ustar 00root root 0000000 0000000 network:
renderer: networkd
vrfs:
vrf1005:
table: 1005
interfaces:
- br1
- br1005
routes:
- to: default
via: 10.10.10.4
routing-policy:
- from: 10.10.10.42
bridges:
br1:
interfaces: []
br1005:
interfaces: []
netplan-0.107.1/examples/vxlan.yaml 0000664 0000000 0000000 00000001255 14536110317 0017157 0 ustar 00root root 0000000 0000000 network:
renderer: networkd
ethernets:
lo:
addresses:
- 192.168.10.10/32
vrfs:
vrf1005:
table: 1005
interfaces:
- br1
- br1005
bridges:
br1:
interfaces:
- vxlan1
br1005:
interfaces:
- vxlan1005
tunnels:
vxlan1005:
mode: vxlan
id: 1005
link: lo
mtu: 8950
accept-ra: no
neigh-suppress: true
mac-learning: false
port: 4789
local: 192.168.10.10
vxlan1:
mode: vxlan
id: 1
link: lo
mtu: 8950
accept-ra: no
neigh-suppress: true
mac-learning: false
port: 4789
local: 192.168.10.10
netplan-0.107.1/examples/windows_dhcp_server.yaml 0000664 0000000 0000000 00000000133 14536110317 0022077 0 ustar 00root root 0000000 0000000 network:
version: 2
ethernets:
enp3s0:
dhcp4: yes
dhcp-identifier: mac
netplan-0.107.1/examples/wireguard.yaml 0000664 0000000 0000000 00000001667 14536110317 0020027 0 ustar 00root root 0000000 0000000 network:
version: 2
tunnels:
wg0: #server
mode: wireguard
addresses: [10.10.10.20/24]
key: 4GgaQCy68nzNsUE5aJ9fuLzHhB65tAlwbmA72MWnOm8=
mark: 42
port: 51820
peers:
- keys:
public: M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG4=
shared: 7voRZ/ojfXgfPOlswo3Lpma1RJq7qijIEEUEMShQFV8=
allowed-ips: [20.20.20.10/24]
routes:
- to: default
via: 10.10.10.21
metric: 100
wg1: #client
mode: wireguard
addresses: [20.20.20.10/24]
key: KPt9BzQjejRerEv8RMaFlpsD675gNexELOQRXt/AcH0=
peers:
- endpoint: 10.10.10.20:51820
allowed-ips: [0.0.0.0/0]
keys:
public: rlbInAj0qV69CysWPQY7KEBnKxpYCpaWqOs/dLevdWc=
shared: 7voRZ/ojfXgfPOlswo3Lpma1RJq7qijIEEUEMShQFV8=
keepalive: 21
routes:
- to: default
via: 20.20.20.11
metric: 200
netplan-0.107.1/examples/wireless.yaml 0000664 0000000 0000000 00000000562 14536110317 0017664 0 ustar 00root root 0000000 0000000 network:
version: 2
renderer: networkd
wifis:
wlp2s0b1:
regulatory-domain: "GB"
dhcp4: no
dhcp6: no
addresses: [192.168.0.21/24]
nameservers:
addresses: [192.168.0.1, 8.8.8.8]
access-points:
"network_ssid_name":
password: "**********"
routes:
- to: default
via: 192.168.0.1
netplan-0.107.1/examples/wireless_adhoc.yaml 0000664 0000000 0000000 00000000673 14536110317 0021025 0 ustar 00root root 0000000 0000000 network:
version: 2
wifis:
wl0:
link-local: [ ipv4 ]
access-points:
"test":
mode: adhoc
band: 2.4GHz
channel: 7
password: "********"
wl1:
addresses: [192.168.2.12/24]
routes:
- to: default
via: 192.168.2.1
access-points:
"test":
mode: adhoc
band: 2.4GHz
channel: 7
password: "********"
netplan-0.107.1/examples/wireless_wpa3.yaml 0000664 0000000 0000000 00000000255 14536110317 0020615 0 ustar 00root root 0000000 0000000 network:
wifis:
wlp1s0:
dhcp4: true
access-points:
"WPA3-Network":
auth:
key-management: sae
password: abcdefgh
netplan-0.107.1/examples/wpa3_enterprise.yaml 0000664 0000000 0000000 00000001577 14536110317 0021150 0 ustar 00root root 0000000 0000000 network:
version: 2
wifis:
wl0:
dhcp4: yes
access-points:
university:
auth:
key-management: eap-sha256
method: tls
anonymous-identity: "@cust.example.com"
identity: "cert-joe@cust.example.com"
ca-certificate: /etc/ssl/cust-cacrt.pem
client-certificate: /etc/ssl/cust-crt.pem
client-key: /etc/ssl/cust-key.pem
client-key-password: "d3cryptPr1v4t3K3y"
enterprise:
auth:
key-management: eap-suite-b-192
method: tls
anonymous-identity: "@cust.example.com"
identity: "cert-joe@cust.example.com"
ca-certificate: /etc/ssl/cust-cacrt.pem
client-certificate: /etc/ssl/cust-crt.pem
client-key: /etc/ssl/cust-key.pem
client-key-password: "d3cryptPr1v4t3K3y"
netplan-0.107.1/examples/wpa_enterprise.yaml 0000664 0000000 0000000 00000001414 14536110317 0021053 0 ustar 00root root 0000000 0000000 network:
version: 2
wifis:
wl0:
access-points:
workplace:
auth:
key-management: eap
method: ttls
anonymous-identity: "@internal.example.com"
identity: "joe@internal.example.com"
password: "v3ryS3kr1t"
university:
auth:
key-management: eap
method: tls
anonymous-identity: "@cust.example.com"
identity: "cert-joe@cust.example.com"
ca-certificate: /etc/ssl/cust-cacrt.pem
client-certificate: /etc/ssl/cust-crt.pem
client-key: /etc/ssl/cust-key.pem
client-key-password: "d3cryptPr1v4t3K3y"
open-network:
auth:
key-management: none
dhcp4: yes
netplan-0.107.1/features_h_generator.sh 0000775 0000000 0000000 00000000531 14536110317 0020053 0 ustar 00root root 0000000 0000000 #!/bin/sh
BASE=$(dirname $0)
OUTPUT=$BASE/src/_features.h
INPUT=$BASE/src/[!_]*.[hc]
printf "#include \nstatic const char *feature_flags[] __attribute__((__unused__)) = {\n" > $OUTPUT
awk 'match ($0, /netplan-feature:.*/ ) { $0=substr($0, RSTART, RLENGTH); print "\""$2"\"," }' $INPUT >> $OUTPUT
echo "NULL, };" >> $OUTPUT
cat $OUTPUT
netplan-0.107.1/features_py_generator.sh 0000775 0000000 0000000 00000000500 14536110317 0020250 0 ustar 00root root 0000000 0000000 #!/bin/sh
BASE=$(dirname $0)
OUTPUT=$BASE/netplan_cli/_features.py
INPUT=$BASE/src/[!_]*.[hc]
echo "# Generated file" > $OUTPUT
echo "NETPLAN_FEATURE_FLAGS = [" >> $OUTPUT
awk 'match ($0, /netplan-feature:.*/ ) { $0=substr($0, RSTART, RLENGTH); print " \""$2"\"," }' $INPUT >> $OUTPUT
echo "]" >> $OUTPUT
cat $OUTPUT
netplan-0.107.1/gcovr.cfg 0000664 0000000 0000000 00000000047 14536110317 0015124 0 ustar 00root root 0000000 0000000 filter = src/*
filter = tests/ctests/*
netplan-0.107.1/include/ 0000775 0000000 0000000 00000000000 14536110317 0014745 5 ustar 00root root 0000000 0000000 netplan-0.107.1/include/meson.build 0000664 0000000 0000000 00000000156 14536110317 0017111 0 ustar 00root root 0000000 0000000 install_headers('netplan.h', 'parse.h', 'parse-nm.h', 'util.h', 'types.h',
subdir: 'netplan')
netplan-0.107.1/include/netplan.h 0000664 0000000 0000000 00000011236 14536110317 0016562 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Canonical, Ltd.
* Author: Lukas Märdian
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
/*! \file netplan.h
* \brief NetplanState and NetplanNetDefinition manipulation.
*/
#pragma once
#include
#include "types.h"
NETPLAN_PUBLIC NetplanState*
netplan_state_new();
NETPLAN_PUBLIC void
netplan_state_reset(NetplanState* np_state);
NETPLAN_PUBLIC void
netplan_state_clear(NetplanState** np_state);
NETPLAN_PUBLIC NetplanBackend
netplan_state_get_backend(const NetplanState* np_state);
NETPLAN_PUBLIC guint
netplan_state_get_netdefs_size(const NetplanState* np_state);
NETPLAN_PUBLIC NetplanNetDefinition*
netplan_state_get_netdef(const NetplanState* np_state, const char* id);
NETPLAN_PUBLIC gboolean
netplan_state_finish_nm_write(
const NetplanState* np_state,
const char* rootdir,
NetplanError** error);
NETPLAN_PUBLIC gboolean
netplan_state_finish_ovs_write(
const NetplanState* np_state,
const char* rootdir,
NetplanError** error);
NETPLAN_PUBLIC gboolean
netplan_state_finish_sriov_write(
const NetplanState* np_state,
const char* rootdir,
NetplanError** error);
/* Write the selected yaml file. All definitions that originate from this file,
* as well as those without any given origin, are written to it.
*/
NETPLAN_PUBLIC gboolean
netplan_state_write_yaml_file(
const NetplanState* np_state,
const char* filename,
const char* rootdir,
NetplanError** error);
/* Update all the YAML files that were used to create this state.
* The definitions without clear origin are written to @default_filename.
*/
NETPLAN_PUBLIC gboolean
netplan_state_update_yaml_hierarchy(
const NetplanState* np_state,
const char* default_filename,
const char* rootdir,
NetplanError** error);
/* Dump the whole yaml configuration into the given file, regardless of the origin
* of each definition.
*/
NETPLAN_PUBLIC gboolean
netplan_state_dump_yaml(
const NetplanState* np_state,
int output_fd,
NetplanError** error);
NETPLAN_PUBLIC gboolean
netplan_netdef_write_yaml(
const NetplanState* np_state,
const NetplanNetDefinition* netdef,
const char* rootdir,
NetplanError** error);
NETPLAN_PUBLIC ssize_t
netplan_netdef_get_filepath(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buffer_size);
NETPLAN_PUBLIC NetplanBackend
netplan_netdef_get_backend(const NetplanNetDefinition* netdef);
NETPLAN_PUBLIC NetplanDefType
netplan_netdef_get_type(const NetplanNetDefinition* netdef);
NETPLAN_PUBLIC ssize_t
netplan_netdef_get_id(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buffer_size);
NETPLAN_PUBLIC NetplanNetDefinition*
netplan_netdef_get_bridge_link(const NetplanNetDefinition* netdef);
NETPLAN_PUBLIC NetplanNetDefinition*
netplan_netdef_get_bond_link(const NetplanNetDefinition* netdef);
NETPLAN_PUBLIC NetplanNetDefinition*
netplan_netdef_get_peer_link(const NetplanNetDefinition* netdef);
NETPLAN_PUBLIC NetplanNetDefinition*
netplan_netdef_get_vlan_link(const NetplanNetDefinition* netdef);
NETPLAN_PUBLIC NetplanNetDefinition*
netplan_netdef_get_sriov_link(const NetplanNetDefinition* netdef);
NETPLAN_PUBLIC ssize_t
netplan_netdef_get_set_name(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buffer_size);
NETPLAN_PUBLIC gboolean
netplan_netdef_has_match(const NetplanNetDefinition* netdef);
NETPLAN_PUBLIC gboolean
netplan_netdef_match_interface(const NetplanNetDefinition* netdef, const char* name, const char* mac, const char* driver_name);
NETPLAN_PUBLIC gboolean
netplan_netdef_get_dhcp4(const NetplanNetDefinition* netdef);
NETPLAN_PUBLIC gboolean
netplan_netdef_get_dhcp6(const NetplanNetDefinition* netdef);
NETPLAN_PUBLIC ssize_t
netplan_netdef_get_macaddress(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buffer_size);
/********** Old API below this ***********/
NETPLAN_DEPRECATED NETPLAN_PUBLIC const char *
netplan_netdef_get_filename(const NetplanNetDefinition* netdef);
NETPLAN_PUBLIC void
write_netplan_conf(const NetplanNetDefinition* def, const char* rootdir);
netplan-0.107.1/include/parse-nm.h 0000664 0000000 0000000 00000002332 14536110317 0016640 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Canonical, Ltd.
* Author: Lukas Märdian
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
/*! \file parse-nm.h
* \brief Parsing native NetworkManager keyfile into Netplan state.
*/
#pragma once
#include "types.h"
#define NETPLAN_NM_EMPTY_GROUP "_"
NETPLAN_PUBLIC gboolean
netplan_parser_load_keyfile(NetplanParser* npp, const char* filename, NetplanError** error);
//TODO: needs to be implemented
//NETPLAN_PUBLIC gboolean
//netplan_parser_load_keyfile_from_fd(NetplanParser* npp, int input_fd, NetplanError** error);
/********** Old API below this ***********/
NETPLAN_PUBLIC gboolean
netplan_parse_keyfile(const char* filename, GError** error);
netplan-0.107.1/include/parse.h 0000664 0000000 0000000 00000004660 14536110317 0016236 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2016 Canonical, Ltd.
* Author: Martin Pitt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
/*! \file parse.h
* \brief Netplan YAML parsing and validation.
*/
#pragma once
#include
#include "types.h"
/****************************************************
* Functions
****************************************************/
NETPLAN_PUBLIC NetplanParser*
netplan_parser_new();
NETPLAN_PUBLIC void
netplan_parser_reset(NetplanParser *npp);
NETPLAN_PUBLIC void
netplan_parser_clear(NetplanParser **npp);
NETPLAN_PUBLIC gboolean
netplan_parser_load_yaml(NetplanParser* npp, const char* filename, NetplanError** error);
NETPLAN_PUBLIC gboolean
netplan_parser_load_yaml_from_fd(NetplanParser* npp, int input_fd, NetplanError** error);
NETPLAN_PUBLIC gboolean
netplan_parser_load_yaml_hierarchy(NetplanParser* npp, const char* rootdir, NetplanError** error);
NETPLAN_PUBLIC gboolean
netplan_parser_load_nullable_fields(NetplanParser* npp, int input_fd, NetplanError** error);
NETPLAN_PUBLIC gboolean
netplan_state_import_parser_results(NetplanState* np_state, NetplanParser* npp, NetplanError** error);
/* Load the overrides, i.e. all global values (like "renderer") or Netdef-IDs
* that are part of the given YAML patch (), and are supposed to be
* overridden inside the yaml hierarchy by the resulting origin_hint file.
* They are supposed to be parsed from the origin-hint file given in
* only. */
NETPLAN_PUBLIC gboolean
netplan_parser_load_nullable_overrides(
NetplanParser* npp, int input_fd, const char* constraint, NetplanError** error);
/********** Old API below this ***********/
NETPLAN_PUBLIC gboolean
netplan_parse_yaml(const char* filename, GError** error);
NETPLAN_PUBLIC GHashTable*
netplan_finish_parse(GError** error);
NETPLAN_PUBLIC guint
netplan_clear_netdefs();
NETPLAN_PUBLIC NetplanBackend
netplan_get_global_backend();
netplan-0.107.1/include/types.h 0000664 0000000 0000000 00000007547 14536110317 0016277 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2022 Canonical, Ltd.
* Author: Danilo Egea Gondolfo
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
/*! \file types.h
* \brief Definition of public types and placeholders.
*/
#pragma once
#define NETPLAN_PUBLIC __attribute__ ((visibility("default")))
#define NETPLAN_INTERNAL __attribute__ ((visibility("default")))
#define NETPLAN_ABI __attribute__ ((visibility("default")))
#define NETPLAN_DEPRECATED __attribute__ ((deprecated))
#define NETPLAN_BUFFER_TOO_SMALL -2
/****************************************************
* Parsed definitions
****************************************************/
#include
typedef enum {
NETPLAN_DEF_TYPE_NONE,
/* physical devices */
NETPLAN_DEF_TYPE_ETHERNET,
NETPLAN_DEF_TYPE_WIFI,
NETPLAN_DEF_TYPE_MODEM,
/* virtual devices */
NETPLAN_DEF_TYPE_VIRTUAL,
NETPLAN_DEF_TYPE_BRIDGE = NETPLAN_DEF_TYPE_VIRTUAL,
NETPLAN_DEF_TYPE_BOND,
NETPLAN_DEF_TYPE_VLAN,
NETPLAN_DEF_TYPE_TUNNEL,
NETPLAN_DEF_TYPE_PORT,
NETPLAN_DEF_TYPE_VRF,
/* Type fallback/passthrough */
NETPLAN_DEF_TYPE_NM,
NETPLAN_DEF_TYPE_DUMMY, /* wokeignore:rule=dummy */
NETPLAN_DEF_TYPE_VETH,
/* Place holder type used to fill gaps when a netdef
* requires links to another netdef (such as vlan_link)
* but it's not strictly mandatory
* It's intended to be used only when renderer is NetworkManager
*/
NETPLAN_DEF_TYPE_NM_PLACEHOLDER_,
NETPLAN_DEF_TYPE_MAX_
} NetplanDefType;
typedef struct netplan_parser NetplanParser;
/**
* Represent a configuration stanza
*/
typedef struct netplan_net_definition NetplanNetDefinition;
typedef struct netplan_state NetplanState;
typedef enum {
NETPLAN_BACKEND_NONE,
NETPLAN_BACKEND_NETWORKD,
NETPLAN_BACKEND_NM,
NETPLAN_BACKEND_OVS,
NETPLAN_BACKEND_MAX_,
} NetplanBackend;
typedef GError NetplanError;
typedef struct _NetplanStateIterator NetplanStateIterator;
struct _NetplanStateIterator {
void* placeholder;
};
/*
* Errors and error domains
*
* NOTE: if new errors or domains are added,
* python-cffi/netplan/_utils.py must be updated with the new entries.
*/
enum NETPLAN_ERROR_DOMAINS {
NETPLAN_PARSER_ERROR = 1,
NETPLAN_VALIDATION_ERROR,
NETPLAN_FILE_ERROR,
NETPLAN_BACKEND_ERROR,
NETPLAN_EMITTER_ERROR,
NETPLAN_FORMAT_ERROR,
};
/*
* Errors for domain NETPLAN_PARSER_ERROR
*
* PARSER_ERRORS are expected to contain the file name, line and column numbers
*/
enum NETPLAN_PARSER_ERRORS {
NETPLAN_ERROR_INVALID_YAML,
NETPLAN_ERROR_INVALID_CONFIG
};
/*
* Errors for domain NETPLAN_VALIDATION_ERROR
*
* VALIDATION_ERRORS are expected to contain only the YAML file name
* where the error was found.
*/
enum NETPLAN_VALIDATION_ERRORS {
NETPLAN_ERROR_CONFIG_GENERIC,
NETPLAN_ERROR_CONFIG_VALIDATION,
};
/*
* Errors for domain NETPLAN_BACKEND_ERROR
*/
enum NETPLAN_BACKEND_ERRORS {
NETPLAN_ERROR_UNSUPPORTED,
NETPLAN_ERROR_VALIDATION,
};
/*
* Errors for domain NETPLAN_EMITTER_ERROR
*/
enum NETPLAN_EMITTER_ERRORS {
NETPLAN_ERROR_YAML_EMITTER,
};
/*
* Errors for domain NETPLAN_FORMAT_ERROR
*
* FORMAT_ERRORS are generic errors emitted from contexts where information
* like the file name is not known.
*/
enum NETPLAN_FORMAT_ERRORS {
NETPLAN_ERROR_FORMAT_INVALID_YAML,
};
netplan-0.107.1/include/util.h 0000664 0000000 0000000 00000006122 14536110317 0016074 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2016 Canonical, Ltd.
* Author: Martin Pitt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
/*! \file util.h
* \brief A set of high-level helper functions that can be used when working
* with datastructures of the library.
*
* For example, it can be used to initialize iterators or clear error structures.
*/
#pragma once
#include
#include
#include "types.h"
/**
* @brief Parses YAML hierarchy from @rootdir, drops the configuration for @id
* from the state and re-generates the YAML files.
*
* @param id The NetplanID for a specific configuration block of network interface(s)
* @param rootdir The location where the YAML hierarchy is read from and written to.
* @return gboolean, Indicating success.
*/
NETPLAN_PUBLIC gboolean
netplan_delete_connection(const char* id, const char* rootdir);
NETPLAN_PUBLIC gboolean
netplan_generate(const char* rootdir);
NETPLAN_PUBLIC ssize_t
netplan_get_id_from_nm_filepath(const char* filename, const char* ssid, char* out_buffer, size_t out_buf_size);
NETPLAN_PUBLIC ssize_t
netplan_netdef_get_output_filename(const NetplanNetDefinition* netdef, const char* ssid, char* out_buffer, size_t out_buf_size);
NETPLAN_PUBLIC void
netplan_error_clear(NetplanError** error);
NETPLAN_PUBLIC ssize_t
netplan_error_message(NetplanError* error, char* buf, size_t buf_size);
/* u64 return value contains both GLib domain and error code. The two values are
* concatenated, so that the relevant data can easily be masked:
* (u32)domain | (u32)code */
NETPLAN_PUBLIC uint64_t
netplan_error_code(NetplanError* error);
NETPLAN_PUBLIC void
netplan_state_iterator_init(const NetplanState* np_state, NetplanStateIterator* iter);
NETPLAN_PUBLIC NetplanNetDefinition*
netplan_state_iterator_next(NetplanStateIterator* iter);
NETPLAN_PUBLIC gboolean
netplan_state_iterator_has_next(const NetplanStateIterator* iter);
NETPLAN_PUBLIC gboolean
netplan_util_create_yaml_patch(const char* conf_obj_path, const char* obj_payload, int out_fd, NetplanError** error);
NETPLAN_PUBLIC gboolean
netplan_util_dump_yaml_subtree(const char* prefix, int input_fd, int output_fd, NetplanError** error);
/********** Old API below this ***********/
/**
* \deprecated Use `netplan_netdef_get_filepath()` instead.
*/
NETPLAN_DEPRECATED NETPLAN_PUBLIC gchar*
netplan_get_filename_by_id(const char* netdef_id, const char* rootdir);
/**
* \deprecated Use `netplan_get_id_from_nm_filepath()` instead.
*/
NETPLAN_DEPRECATED NETPLAN_PUBLIC gchar*
netplan_get_id_from_nm_filename(const char* filename, const char* ssid);
netplan-0.107.1/meson.build 0000664 0000000 0000000 00000011673 14536110317 0015474 0 ustar 00root root 0000000 0000000 project('netplan', 'c',
version: '0.107',
license: 'GPL3',
default_options: [
'c_std=c99',
'warning_level=2',
'werror=true',
],
meson_version: '>= 0.61.0',
)
glib = dependency('glib-2.0')
gio = dependency('gio-2.0')
yaml = dependency('yaml-0.1')
uuid = dependency('uuid')
libsystemd = dependency('libsystemd')
meson_make_symlink = meson.current_source_dir() + '/tools/meson-make-symlink.sh'
systemd = dependency('systemd')
completions = dependency('bash-completion')
systemd_generator_dir = systemd.get_variable(pkgconfig: 'systemdsystemgeneratordir')
bash_completions_dir = completions.get_variable(pkgconfig: 'completionsdir', default_value: '/etc/bash_completion.d')
# Order: Fedora/Mageia/openSUSE || Debian/Ubuntu
pyflakes = find_program('pyflakes-3', 'pyflakes3', required: false)
pycodestyle = find_program('pycodestyle-3', 'pycodestyle', 'pep8', required: false)
pytest = find_program('pytest-3', 'pytest3') # also requires the pytest-cov plugin
pycoverage = find_program('coverage-3', 'python3-coverage')
pandoc = find_program('pandoc', required: false)
find = find_program('find')
add_project_arguments(
'-DSBINDIR="' + join_paths(get_option('prefix'), get_option('sbindir')) + '"',
'-D_GNU_SOURCE',
language: 'c')
inc = include_directories('include')
inc_internal = include_directories('src')
subdir('include')
subdir('src')
subdir('dbus')
subdir('netplan_cli')
subdir('python-cffi')
subdir('examples')
subdir('doc')
pkg_mod = import('pkgconfig')
pkg_mod.generate(
libraries: libnetplan,
subdirs: ['netplan'],
name: 'libnetplan',
filebase: 'netplan',
description: 'YAML network configuration abstraction runtime library')
install_data(
'netplan.completions',
rename: 'netplan',
install_dir: bash_completions_dir)
###########
# Testing #
###########
test_env = [
'PYTHONPATH=' + join_paths(meson.current_build_dir(), 'python-cffi') + ':' + meson.current_source_dir(),
'LD_LIBRARY_PATH=' + join_paths(meson.current_build_dir(), 'src'),
'NETPLAN_GENERATE_PATH=' + join_paths(meson.current_build_dir(), 'src', 'generate'),
'NETPLAN_DBUS_CMD=' + join_paths(meson.current_build_dir(), 'dbus', 'netplan-dbus'),
'COVERAGE_PROCESS_START=' + join_paths(meson.current_source_dir(), '.coveragerc'),
'G_DEBUG=fatal_criticals',
]
if get_option('unit_testing')
subdir('tests/ctests')
endif
#FIXME: exclude doc/env/
test('linting',
pyflakes,
timeout: 100,
args: [meson.current_source_dir()])
test('codestyle',
pycodestyle,
timeout: 100,
args: ['--max-line-length=130', '--exclude=doc/env,*meson-private/pycompile.py', meson.current_source_dir()])
test('documentation',
find_program('tests/validate_docs.sh'),
timeout: 100,
workdir: meson.current_source_dir())
test('legacy-tests',
find_program('tests/cli_legacy.py'),
timeout: 600,
env: test_env)
#TODO: split out dbus tests into own test() instance, to run in parallel
test('unit-tests',
pycoverage,
args: ['run', '-a', '-m', 'pytest', '-s', '-v', '--cov-append', meson.current_source_dir()],
timeout: 600,
env: test_env)
#TODO: the coverage section should probably be cleaned up a bit
if get_option('b_coverage')
message('Find coverage reports in /meson-logs/coveragereport[-py]/')
# Using gcovr instead of lcov/gcov.
# The 'ninja coverage' command will produce the html/txt reports for C implicitly
#lcov = find_program('lcov')
#gcov = find_program('gcov')
#genhtml = find_program('genhtml')
gcovr = find_program('gcovr')
ninja = find_program('ninja')
grep = find_program('grep')
cat = find_program('cat')
test('coverage-c-output',
find_program('ninja'),
args: ['-C', meson.current_build_dir(), 'coverage'],
timeout: 60,
priority: -90, # run before 'coverage-c'
is_parallel: false)
test('coverage-c-cat',
cat,
args: [join_paths(meson.current_build_dir(), 'meson-logs', 'coverage.txt')],
priority: -98, # run before 'coverage-c'
is_parallel: false)
test('coverage-c',
grep,
args: ['^TOTAL.*100%$', join_paths(meson.current_build_dir(), 'meson-logs', 'coverage.txt')],
priority: -99, # run last
is_parallel: false)
test('coverage-py-combine',
pycoverage,
args: ['combine', '-a', meson.current_build_dir()],
priority: -90, # run before 'coverage-py-output'
is_parallel: false)
test('coverage-py-output',
pycoverage,
args: ['html', '-d', join_paths(meson.current_build_dir(),
'meson-logs', 'coveragereport-py'), '--omit=/usr/*'],
priority: -95, # run before 'coverage-py'
is_parallel: false)
test('coverage-py',
pycoverage,
args: ['report', '--omit=/usr/*', '--show-missing', '--fail-under=100'],
priority: -99, # run last
is_parallel: false)
endif
netplan-0.107.1/meson_options.txt 0000664 0000000 0000000 00000000065 14536110317 0016760 0 ustar 00root root 0000000 0000000 option('unit_testing', type: 'boolean', value: true)
netplan-0.107.1/netplan.completions 0000664 0000000 0000000 00000005336 14536110317 0017250 0 ustar 00root root 0000000 0000000 # netplan(5) completion -*- shell-script -*-
_netplan_completions_filter() {
local words="$1"
local cur=${COMP_WORDS[COMP_CWORD]}
local result=()
if [[ "${cur:0:1}" == "-" ]]; then
echo "$words"
else
for word in $words; do
[[ "${word:0:1}" != "-" ]] && result+=("$word")
done
echo "${result[*]}"
fi
}
_netplan_completions() {
local cur=${COMP_WORDS[COMP_CWORD]}
local compwords=("${COMP_WORDS[@]:1:$COMP_CWORD-1}")
local compline="${compwords[*]}"
case "$compline" in
'ip leases'*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help --debug --root-dir")" -- "$cur" )
;;
'generate'*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help --debug --root-dir --mapping")" -- "$cur" )
;;
'rebind'*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help --debug")" -- "$cur" )
;;
'status'*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help --debug -a --all -f --format $(ls /sys/class/net 2> /dev/null)")" -- "$cur" )
;;
'apply'*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help --debug --sriov-only --only-ovs-cleanup --state")" -- "$cur" )
;;
'help'*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help")" -- "$cur" )
;;
'info'*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help --debug --json --yaml")" -- "$cur" )
;;
'get'*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help --debug --root-dir")" -- "$cur" )
;;
'set'*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help --debug --origin-hint")" -- "$cur" )
;;
'try'*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help --debug --config-file --timeout --state")" -- "$cur" )
;;
'ip'*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help --debug help leases")" -- "$cur" )
;;
*)
while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "$(_netplan_completions_filter "-h --help --debug help apply generate get info ip set rebind status try")" -- "$cur" )
;;
esac
} &&
complete -F _netplan_completions netplan
# ex: filetype=sh
netplan-0.107.1/netplan_cli/ 0000775 0000000 0000000 00000000000 14536110317 0015612 5 ustar 00root root 0000000 0000000 netplan-0.107.1/netplan_cli/__init__.py 0000664 0000000 0000000 00000001365 14536110317 0017730 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
from .cli.core import Netplan
__all__ = [Netplan]
netplan-0.107.1/netplan_cli/cli/ 0000775 0000000 0000000 00000000000 14536110317 0016361 5 ustar 00root root 0000000 0000000 netplan-0.107.1/netplan_cli/cli/__init__.py 0000664 0000000 0000000 00000001301 14536110317 0020465 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
netplan-0.107.1/netplan_cli/cli/commands/ 0000775 0000000 0000000 00000000000 14536110317 0020162 5 ustar 00root root 0000000 0000000 netplan-0.107.1/netplan_cli/cli/commands/__init__.py 0000664 0000000 0000000 00000002350 14536110317 0022273 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
from .apply import NetplanApply
from .generate import NetplanGenerate
from .ip import NetplanIp
from .migrate import NetplanMigrate
from .try_command import NetplanTry
from .info import NetplanInfo
from .set import NetplanSet
from .get import NetplanGet
from .sriov_rebind import NetplanSriovRebind
from .status import NetplanStatus
__all__ = [
'NetplanApply',
'NetplanGenerate',
'NetplanIp',
'NetplanMigrate',
'NetplanTry',
'NetplanInfo',
'NetplanSet',
'NetplanGet',
'NetplanSriovRebind',
'NetplanStatus',
]
netplan-0.107.1/netplan_cli/cli/commands/apply.py 0000664 0000000 0000000 00000051753 14536110317 0021674 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018-2020 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
# Author: Łukasz 'sil2100' Zemczak
# Author: Lukas 'slyon' Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan apply command line'''
import logging
import os
import sys
import glob
import subprocess
import shutil
import netifaces
import time
from .. import utils
from ...configmanager import ConfigManager, ConfigurationError
from ..sriov import apply_sriov_config
from ..ovs import OvsDbServerNotRunning, apply_ovs_cleanup
OVS_CLEANUP_SERVICE = 'netplan-ovs-cleanup.service'
IF_NAMESIZE = 16
class NetplanApply(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='apply',
description='Apply current netplan config to running system',
leaf=True)
self.sriov_only = False
self.only_ovs_cleanup = False
self.state = None # to be filled by the '--state' argument
def run(self): # pragma: nocover (covered in autopkgtest)
self.parser.add_argument('--sriov-only', action='store_true',
help='Only apply SR-IOV related configuration and exit')
self.parser.add_argument('--only-ovs-cleanup', action='store_true',
help='Only clean up old OpenVSwitch interfaces and exit')
self.parser.add_argument('--state',
help='Directory containing previous YAML configuration')
self.func = self.command_apply
self.parse_args()
self.run_command()
def command_apply(self, run_generate=True, sync=False, exit_on_error=True, state_dir=None): # pragma: nocover
config_manager = ConfigManager()
if state_dir:
self.state = state_dir
# For certain use-cases, we might want to only apply specific configuration.
# If we only need SR-IOV configuration, do that and exit early.
if self.sriov_only:
NetplanApply.process_sriov_config(config_manager, exit_on_error)
return
# If we only need OpenVSwitch cleanup, do that and exit early.
elif self.only_ovs_cleanup:
NetplanApply.process_ovs_cleanup(config_manager, False, False, exit_on_error)
return
# if we are inside a snap, then call dbus to run netplan apply instead
if "SNAP" in os.environ:
# TODO: maybe check if we are inside a classic snap and don't do
# this if we are in a classic snap?
busctl = shutil.which("busctl")
if busctl is None:
raise RuntimeError("missing busctl utility")
# XXX: DO NOT TOUCH or change this API call, it is used by snapd to communicate
# using core20 netplan binary/client/CLI on core18 base systems. Any change
# must be agreed upon with the snapd team, so we don't break support for
# base systems running older netplan versions.
# https://github.com/snapcore/snapd/pull/5915
res = subprocess.call([busctl, "call", "--quiet", "--system",
"io.netplan.Netplan", # the service
"/io/netplan/Netplan", # the object
"io.netplan.Netplan", # the interface
"Apply", # the method
])
if res != 0:
if exit_on_error:
sys.exit(res)
elif res == 130:
raise PermissionError(
"failed to communicate with dbus service")
else:
raise RuntimeError(
"failed to communicate with dbus service: error %s" % res)
else:
return
ovs_cleanup_service = '/run/systemd/system/netplan-ovs-cleanup.service'
old_files_networkd = bool(glob.glob('/run/systemd/network/*netplan-*'))
old_ovs_glob = glob.glob('/run/systemd/system/netplan-ovs-*')
# Ignore netplan-ovs-cleanup.service, as it can always be there
if ovs_cleanup_service in old_ovs_glob:
old_ovs_glob.remove(ovs_cleanup_service)
old_files_ovs = bool(old_ovs_glob)
old_nm_glob = glob.glob('/run/NetworkManager/system-connections/netplan-*')
nm_ifaces = utils.nm_interfaces(old_nm_glob, netifaces.interfaces())
old_files_nm = bool(old_nm_glob)
generator_call = []
generate_out = None
if 'NETPLAN_PROFILE' in os.environ:
generator_call.extend(['valgrind', '--leak-check=full'])
generate_out = subprocess.STDOUT
generator_call.append(utils.get_generator_path())
if run_generate and subprocess.call(generator_call, stderr=generate_out) != 0:
if exit_on_error:
sys.exit(os.EX_CONFIG)
else:
raise ConfigurationError("the configuration could not be generated")
devices = netifaces.interfaces()
# Re-start service when
# 1. We have configuration files for it
# 2. Previously we had config files for it but not anymore
# Ideally we should compare the content of the *netplan-* files before and
# after generation to minimize the number of re-starts, but the conditions
# above works too.
restart_networkd = bool(glob.glob('/run/systemd/network/*netplan-*'))
if not restart_networkd and old_files_networkd:
restart_networkd = True
restart_ovs_glob = glob.glob('/run/systemd/system/netplan-ovs-*')
# Ignore netplan-ovs-cleanup.service, as it can always be there
if ovs_cleanup_service in restart_ovs_glob:
restart_ovs_glob.remove(ovs_cleanup_service)
restart_ovs = bool(restart_ovs_glob)
if not restart_ovs and old_files_ovs:
# OVS is managed via systemd units
restart_networkd = True
restart_nm_glob = glob.glob('/run/NetworkManager/system-connections/netplan-*')
nm_ifaces.update(utils.nm_interfaces(restart_nm_glob, devices))
restart_nm = bool(restart_nm_glob)
if not restart_nm and old_files_nm:
restart_nm = True
# Running 'systemctl daemon-reload' will re-run the netplan systemd generator,
# so let's make sure we only run it iff we're willing to run 'netplan generate'
if run_generate:
utils.systemctl_daemon_reload()
# stop backends
if restart_networkd:
logging.debug('netplan generated networkd configuration changed, reloading networkd')
# Clean up any old netplan related OVS ports/bonds/bridges, if applicable
NetplanApply.process_ovs_cleanup(config_manager, old_files_ovs, restart_ovs, exit_on_error)
wpa_services = ['netplan-wpa-*.service']
# Historically (up to v0.98) we had netplan-wpa@*.service files, in case of an
# upgraded system, we need to make sure to stop those.
if utils.systemctl_is_active('netplan-wpa@*.service'):
wpa_services.insert(0, 'netplan-wpa@*.service')
utils.systemctl('stop', wpa_services, sync=sync)
else:
logging.debug('no netplan generated networkd configuration exists')
loopback_connection = ''
if restart_nm:
logging.debug('netplan generated NM configuration changed, restarting NM')
if utils.nm_running():
if 'lo' in nm_ifaces:
loopback_connection = utils.nm_get_connection_for_interface('lo')
# restarting NM does not cause new config to be applied, need to shut down devices first
for device in devices:
if device not in nm_ifaces:
continue # do not touch this interface
# ignore failures here -- some/many devices might not be managed by NM
try:
utils.nmcli(['device', 'disconnect', device])
except subprocess.CalledProcessError:
pass
utils.systemctl_network_manager('stop', sync=sync)
else:
logging.debug('no netplan generated NM configuration exists')
# Refresh devices now; restarting a backend might have made something appear.
devices = netifaces.interfaces()
# evaluate config for extra steps we need to take (like renaming)
# for now, only applies to non-virtual (real) devices.
config_manager.parse()
changes = NetplanApply.process_link_changes(devices, config_manager)
# delete virtual interfaces that have been defined in a previous state
# but are not configured anymore in the current YAML
if self.state:
cm = ConfigManager(self.state)
cm.parse() # get previous configuration state
prev_links = cm.virtual_interfaces.keys()
curr_links = config_manager.virtual_interfaces.keys()
NetplanApply.clear_virtual_links(prev_links, curr_links, devices)
# if the interface is up, we can still apply some .link file changes
# but we cannot apply the interface rename via udev, as it won't touch
# the interface name, if it was already renamed once (e.g. during boot),
# because of the NamePolicy=keep default:
# https://www.freedesktop.org/software/systemd/man/systemd.net-naming-scheme.html
devices = netifaces.interfaces()
for device in devices:
logging.debug('netplan triggering .link rules for %s', device)
try:
subprocess.check_call(['udevadm', 'test-builtin',
'net_setup_link',
'/sys/class/net/' + device],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
subprocess.check_call(['udevadm', 'test',
'/sys/class/net/' + device],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError:
logging.debug('Ignoring device without syspath: %s', device)
devices_after_udev = netifaces.interfaces()
# apply some more changes manually
for iface, settings in changes.items():
# rename non-critical network interfaces
new_name = settings.get('name')
if new_name:
if len(new_name) >= IF_NAMESIZE:
logging.warning('Interface name {} is too long. {} will not be renamed'.format(new_name, iface))
continue
if iface in devices and new_name in devices_after_udev:
logging.debug('Interface rename {} -> {} already happened.'.format(iface, new_name))
continue # re-name already happened via 'udevadm test'
# bring down the interface, using its current (matched) interface name
subprocess.check_call(['ip', 'link', 'set', 'dev', iface, 'down'],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
# rename the interface to the name given via 'set-name'
subprocess.check_call(['ip', 'link', 'set',
'dev', iface,
'name', settings.get('name')],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
# Reloading of udev rules happens during 'netplan generate' already
# subprocess.check_call(['udevadm', 'control', '--reload-rules'])
subprocess.check_call(['udevadm', 'trigger', '--attr-match=subsystem=net'])
subprocess.check_call(['udevadm', 'settle'])
# apply any SR-IOV related changes, if applicable
NetplanApply.process_sriov_config(config_manager, exit_on_error)
# (re)set global regulatory domain
if os.path.exists('/run/systemd/system/netplan-regdom.service'):
utils.systemctl('start', ['netplan-regdom.service'])
# (re)start backends
if restart_networkd:
netplan_wpa = [os.path.basename(f) for f in glob.glob('/run/systemd/system/*.wants/netplan-wpa-*.service')]
# exclude the special 'netplan-ovs-cleanup.service' unit
netplan_ovs = [os.path.basename(f) for f in glob.glob('/run/systemd/system/*.wants/netplan-ovs-*.service')
if not f.endswith('/' + OVS_CLEANUP_SERVICE)]
# Run 'systemctl start' command synchronously, to avoid race conditions
# with 'oneshot' systemd service units, e.g. netplan-ovs-*.service.
try:
utils.networkctl_reload()
utils.networkctl_reconfigure(utils.networkd_interfaces())
except subprocess.CalledProcessError:
# (re-)start systemd-networkd if it is not running, yet
logging.warning('Falling back to a hard restart of systemd-networkd.service')
utils.systemctl('restart', ['systemd-networkd.service'], sync=True)
# 1st: execute OVS cleanup, to avoid races while applying OVS config
utils.systemctl('start', [OVS_CLEANUP_SERVICE], sync=True)
# 2nd: start all other services
utils.systemctl('start', netplan_wpa + netplan_ovs, sync=True)
if restart_nm:
# Flush all IP addresses of NM managed interfaces, to avoid NM creating
# new, non netplan-* connection profiles, using the existing IPs.
nm_interfaces = utils.nm_interfaces(restart_nm_glob, devices)
for iface in nm_interfaces:
utils.ip_addr_flush(iface)
# clear NM state, especially the [device].managed=true config, as that might have been
# re-set via an udev rule setting "NM_UNMANAGED=1"
shutil.rmtree('/run/NetworkManager/devices', ignore_errors=True)
utils.systemctl_network_manager('start', sync=sync)
# If 'lo' is in the nm_interfaces set we flushed it's IPs (see above) and disconnected it.
# NM will not bring it back automatically after restarting and we need to do that manually.
# For that, we need NM up and ready to accept commands
if 'lo' in nm_interfaces:
sync = True
if sync:
# 'nmcli' could be /usr/bin/nmcli or
# /snap/bin/nmcli -> /snap/bin/network-manager.nmcli
cmd = ['nmcli', 'general', 'status']
# wait a bit for 'connected (site/local-only)' or
# 'connected' to appear in 'nmcli general' STATE
for _ in range(10):
out = subprocess.run(cmd, capture_output=True, text=True)
# Handle nmcli's "not running" return code (8) gracefully,
# giving some more time for NetworkManager startup
if out.returncode == 8:
time.sleep(1)
continue
if '\nconnected' in str(out.stdout):
break
time.sleep(0.5)
# If "lo" is managed by NM through Netplan, apply will flush its addresses and disconnect it.
# NM will not bring it back automatically.
# This is a possible scenario with netplan-everywhere. If a user tries to change the 'lo'
# connection with nmcli for example, NM will create a persistent nmconnection file and emit a YAML for it.
if 'lo' in nm_interfaces and loopback_connection:
utils.nm_bring_interface_up(loopback_connection)
@staticmethod
def is_composite_member(composites, phy):
"""
Is this physical interface a member of a 'composite' virtual
interface? (bond, bridge)
"""
for composite in composites:
for _, settings in composite.items():
if not type(settings) is dict:
continue
members = settings.get('interfaces', [])
for iface in members:
if iface == phy:
return True
return False
@staticmethod
def clear_virtual_links(prev_links, curr_links, devices=[]):
"""
Calculate the delta of virtual links. And remove the links that were
dropped from the YAML config, if they were not dropped by the backend
already.
We can make use of the netplan netdef ids, as those equal the interface
name for virtual links.
"""
if not devices:
logging.warning('Cannot clear virtual links: no network interfaces provided.')
return []
dropped_interfaces = list(set(prev_links) - set(curr_links))
# some interfaces might have been cleaned up already, e.g. by the
# NetworkManager backend
interfaces_to_clear = list(set(dropped_interfaces).intersection(devices))
for link in interfaces_to_clear:
try:
cmd = ['ip', 'link', 'delete', 'dev', link]
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
logging.warning('Could not delete interface {}'.format(link))
return dropped_interfaces
@staticmethod
def process_link_changes(interfaces, config_manager: ConfigManager): # pragma: nocover (covered in autopkgtest)
"""
Go through the pending changes and pick what needs special handling.
Only applies to non-critical interfaces which can be safely updated.
"""
changes = {}
composite_interfaces = [config_manager.bridges, config_manager.bonds]
# Find physical interfaces which need a rename
# But do not rename virtual interfaces
for netdef in config_manager.physical_interfaces.values():
newname = netdef.set_name
if not newname:
continue # Skip if no new name needs to be set
if not netdef._has_match:
continue # Skip if no match for current name is given
if NetplanApply.is_composite_member(composite_interfaces, netdef.id):
logging.debug('Skipping composite member {}'.format(netdef.id))
# do not rename members of virtual devices. MAC addresses
# may be the same for all interface members.
continue
# Find current name of the interface, according to match conditions and globs (name, mac, driver)
current_iface_name = utils.find_matching_iface(interfaces, netdef)
if not current_iface_name:
logging.warning('Cannot find unique matching interface for {}'.format(netdef.id))
continue
if current_iface_name == newname:
# Skip interface if it already has the correct name
logging.debug('Skipping correctly named interface: {}'.format(newname))
continue
if netdef.critical:
# Skip interfaces defined as critical, as we should not take them down in order to rename
logging.warning('Cannot rename {} ({} -> {}) at runtime (needs reboot), due to being critical'
.format(netdef.id, current_iface_name, newname))
continue
# record the interface rename change
changes[current_iface_name] = {'name': newname}
logging.debug('Link changes: {}'.format(changes))
return changes
@staticmethod
def process_sriov_config(config_manager, exit_on_error=True): # pragma: nocover (covered in autopkgtest)
try:
apply_sriov_config(config_manager)
except utils.config_errors as e:
logging.error(str(e))
if exit_on_error:
sys.exit(1)
@staticmethod
def process_ovs_cleanup(config_manager, ovs_old, ovs_current, exit_on_error=True): # pragma: nocover (autopkgtest)
try:
apply_ovs_cleanup(config_manager, ovs_old, ovs_current)
except (OSError, RuntimeError) as e:
logging.error(str(e))
if exit_on_error:
sys.exit(1)
except OvsDbServerNotRunning as e:
logging.warning('Cannot call Open vSwitch: {}.'.format(e))
netplan-0.107.1/netplan_cli/cli/commands/generate.py 0000664 0000000 0000000 00000006744 14536110317 0022341 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan generate command line'''
import logging
import os
import sys
import subprocess
import shutil
from .. import utils
class NetplanGenerate(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='generate',
description='Generate backend specific configuration files'
' from /etc/netplan/*.yaml',
leaf=True)
def run(self):
self.parser.add_argument('--root-dir',
help='Search for and generate configuration files in this root directory instead of /')
self.parser.add_argument('--mapping',
help='Display the netplan device ID/backend/interface name mapping and exit.')
self.func = self.command_generate
self.parse_args()
self.run_command()
def command_generate(self):
# if we are inside a snap, then call dbus to run netplan apply instead
if "SNAP" in os.environ:
# TODO: maybe check if we are inside a classic snap and don't do
# this if we are in a classic snap?
busctl = shutil.which("busctl")
if busctl is None:
raise RuntimeError("missing busctl utility") # pragma: nocover
# XXX: DO NOT TOUCH or change this API call, it is used by snapd to communicate
# using core20 netplan binary/client/CLI on core18 base systems. Any change
# must be agreed upon with the snapd team, so we don't break support for
# base systems running older netplan versions.
# https://github.com/snapcore/snapd/pull/10212
res = subprocess.call([busctl, "call", "--quiet", "--system",
"io.netplan.Netplan", # the service
"/io/netplan/Netplan", # the object
"io.netplan.Netplan", # the interface
"Generate", # the method
])
if res != 0:
if res == 130:
raise PermissionError(
"PermissionError: failed to communicate with dbus service")
else:
raise RuntimeError(
"RuntimeError: failed to communicate with dbus service: error %s" % res)
else:
return
argv = [utils.get_generator_path()]
if self.root_dir:
argv += ['--root-dir', self.root_dir]
if self.mapping:
argv += ['--mapping', self.mapping]
logging.debug('command generate: running %s', argv)
# FIXME: os.execv(argv[0], argv) would be better but fails coverage
sys.exit(subprocess.call(argv))
netplan-0.107.1/netplan_cli/cli/commands/get.py 0000664 0000000 0000000 00000003030 14536110317 0021307 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2020-2023 Canonical, Ltd.
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan get command line'''
from ..state import NetplanConfigState
from .. import utils
class NetplanGet(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='get',
description='Get a setting by specifying a nested key like "ethernets.eth0.addresses", or "all"',
leaf=True)
def run(self):
self.parser.add_argument('key', type=str, nargs='?', default='all', help='The nested key in dotted format')
self.parser.add_argument('--root-dir', default='/',
help='Read configuration files from this root directory instead of /')
self.func = self.command_get
self.parse_args()
self.run_command()
def command_get(self):
state_data = NetplanConfigState(self.key, self.root_dir)
print(state_data, end='')
netplan-0.107.1/netplan_cli/cli/commands/info.py 0000664 0000000 0000000 00000004636 14536110317 0021500 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2019 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan info command line'''
from .. import utils
from ... import _features
class NetplanInfo(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='info',
description='Show available features',
leaf=True)
def run(self): # pragma: nocover (covered in autopkgtest)
format_group = self.parser.add_mutually_exclusive_group(required=False)
format_group.add_argument('--json', dest='version_format', action='store_const',
const='json',
help='Output version and features in JSON format')
format_group.add_argument('--yaml', dest='version_format', action='store_const',
const='yaml',
help='Output version and features in YAML format')
self.func = self.command_info
self.parse_args()
self.run_command()
def command_info(self):
netplan_version = {
'netplan.io': {
'website': 'https://netplan.io/',
}
}
flags = _features.NETPLAN_FEATURE_FLAGS
netplan_version['netplan.io'].update({'features': flags})
# Default to output in YAML format.
if self.version_format is None:
self.version_format = 'yaml'
if self.version_format == 'json':
import json
print(json.dumps(netplan_version, indent=2))
elif self.version_format == 'yaml':
print('''netplan.io:
website: "{}"
features:'''.format(netplan_version['netplan.io']['website']))
for feature in _features.NETPLAN_FEATURE_FLAGS:
print(' - ' + feature)
netplan-0.107.1/netplan_cli/cli/commands/ip.py 0000664 0000000 0000000 00000014126 14536110317 0021150 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan ip command line'''
import logging
import os
import sys
import subprocess
from subprocess import CalledProcessError
from .. import utils
lease_path = {
'networkd': {
'pattern': 'run/systemd/netif/leases/{lease_id}',
'method': 'ifindex',
},
'NetworkManager': {
'pattern': 'var/lib/NetworkManager/internal-{lease_id}-{interface}.lease',
'method': 'nm_connection',
},
}
class NetplanIp(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='ip',
description='Retrieve IP information from the system',
leaf=False)
def run(self):
self.command_leases = NetplanIpLeases()
# subcommand: leases
p_ip_leases = self.subparsers.add_parser('leases',
help='Display IP leases',
add_help=False)
p_ip_leases.set_defaults(func=self.command_leases.run, commandclass=self.command_leases)
self.parse_args()
self.run_command()
class NetplanIpLeases(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='ip leases',
description='Display IP leases',
leaf=True)
def run(self):
self.parser.add_argument('interface',
help='Interface for which to display IP lease settings.')
self.parser.add_argument('--root-dir',
help='Search for configuration files in this root directory instead of /')
self.func = self.command_ip_leases
self.parse_args()
self.run_command()
def command_ip_leases(self):
if self.interface == 'help': # pragma: nocover (covered in autopkgtest)
self.print_usage()
def find_lease_file(mapping):
def lease_method_ifindex():
ifindex_f = os.path.join('/sys/class/net', self.interface, 'ifindex')
try:
with open(ifindex_f) as f:
return f.readlines()[0].strip()
except Exception as e:
logging.debug('Cannot read file %s: %s', ifindex_f, str(e))
raise
def lease_method_nm_connection():
# FIXME: handle older versions of NM where 'nmcli dev show' doesn't exist
try:
nmcli_dev_out = utils.nmcli_out(['dev', 'show', self.interface])
for line in nmcli_dev_out.splitlines():
if 'GENERAL.CONNECTION' in line:
conn_id = line.split(':')[1].rstrip().strip()
nmcli_con_out = utils.nmcli_out(['con', 'show', 'id', conn_id])
for line in nmcli_con_out.splitlines():
if 'connection.uuid' in line:
return line.split(':')[1].rstrip().strip()
except Exception as e:
raise Exception('Could not find a NetworkManager connection for the interface: %s' % str(e))
raise Exception('Could not find a NetworkManager connection for the interface')
lease_pattern = lease_path[mapping['backend']]['pattern']
lease_method = lease_path[mapping['backend']]['method']
try:
lease_id = eval("lease_method_" + lease_method)()
# We found something to build the path to the lease file with,
# at this point we may have something to look at; but if not,
# we'll rely on open() throwing an error.
# This might happen if networkd doesn't use DHCP for the interface,
# for instance.
path = os.path.join('/',
os.path.abspath(self.root_dir) if self.root_dir else "",
lease_pattern.format(interface=self.interface,
lease_id=lease_id))
# Fallback to 'dhclient' if no lease of NetworkManager's
# internal DHCP client is found
if not os.path.isfile(path):
path = path.replace('NetworkManager/internal-', 'NetworkManager/dhclient-')
with open(path) as f:
for line in f.readlines():
print(line.rstrip())
except Exception as e:
print("No lease found for interface '%s': %s" % (self.interface, str(e)),
file=sys.stderr)
sys.exit(1)
argv = [utils.get_generator_path()]
if self.root_dir:
argv += ['--root-dir', self.root_dir]
argv += ['--mapping', self.interface]
# Extract out of the generator our mapping in a dict.
logging.debug('command ip leases: running %s', argv)
try:
out = subprocess.check_output(argv, text=True)
except CalledProcessError: # pragma: nocover (better be covered in autopkgtest)
print("No lease found for interface '%s' (not managed by Netplan)" % self.interface, file=sys.stderr)
sys.exit(1)
mapping = {}
mapping_s = out.split(',')
for keyvalue in mapping_s:
key, value = keyvalue.strip().split('=')
mapping[key] = value
find_lease_file(mapping)
netplan-0.107.1/netplan_cli/cli/commands/migrate.py 0000664 0000000 0000000 00000047746 14536110317 0022206 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan migrate command line'''
import logging
import os
import sys
import re
from glob import glob
try:
import yaml
NO_YAML = False
except ImportError: # pragma: nocover
NO_YAML = True
from collections import OrderedDict
import ipaddress
from .. import utils
class NetplanMigrate(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='migrate',
description='Migration of /etc/network/interfaces to netplan',
leaf=True,
testing=True)
def parse_dns_options(self, if_options, if_config):
"""Parse dns options (dns-nameservers and dns-search) from if_options
(an interface options dict) into the interface configuration if_config
Mutates the arguments in place.
"""
if 'dns-nameservers' in if_options:
if 'nameservers' not in if_config:
if_config['nameservers'] = {}
if 'addresses' not in if_config['nameservers']:
if_config['nameservers']['addresses'] = []
for ns in if_options['dns-nameservers'].split(' '):
# allow multiple spaces in the dns-nameservers entry
if not ns:
continue
# validate?
if_config['nameservers']['addresses'] += [ns]
del if_options['dns-nameservers']
if 'dns-search' in if_options:
if 'nameservers' not in if_config:
if_config['nameservers'] = {}
if 'search' not in if_config['nameservers']:
if_config['nameservers']['search'] = []
for domain in if_options['dns-search'].split(' '):
# allow multiple spaces in the dns-search entry
if not domain:
continue
if_config['nameservers']['search'] += [domain]
del if_options['dns-search']
def parse_mtu(self, iface, if_options, if_config):
"""Parse out the MTU. Operates the same way as parse_dns_options
iface is the name of the interface, used only to print error messages
"""
if 'mtu' in if_options:
try:
mtu = int(if_options['mtu'])
except ValueError:
logging.error('%s: cannot parse "%s" as an MTU', iface, if_options['mtu'])
sys.exit(2)
if 'mtu' in if_config and not if_config['mtu'] == mtu:
logging.error('%s: tried to set MTU=%d, but already have MTU=%d', iface, mtu, if_config['mtu'])
sys.exit(2)
if_config['mtu'] = mtu
del if_options['mtu']
def parse_hwaddress(self, iface, if_options, if_config):
"""Parse out the manually configured MAC.
Operates the same way as parse_dns_options
iface is the name of the interface, used only to print error messages
"""
if 'hwaddress' in if_options:
if 'macaddress' in if_config and not if_config['macaddress'] == if_options['hwaddress']:
logging.error('%s: tried to set MAC %s, but already have MAC %s', iface,
if_options['hwaddress'], if_config['macaddress'])
sys.exit(2)
if_config['macaddress'] = if_options['hwaddress']
del if_options['hwaddress']
def run(self):
self.parser.add_argument('--root-dir',
help='Search for and generate configuration files in this root directory instead of /')
self.parser.add_argument('--dry-run', action='store_true',
help='Print converted netplan configuration to stdout instead of writing/changing files')
self.func = self.command_migrate
self.parse_args()
if NO_YAML: # pragma: nocover
logging.error("""The `yaml` Python package couldn't be imported, and is needed for the migrate command.
To install it on Debian or Ubuntu-based system, run `apt install python3-yaml`""")
sys.exit(1)
self.run_command()
def command_migrate(self):
netplan_config = {}
try:
ifaces, auto_ifaces = self.parse_ifupdown(self.root_dir or '')
except ValueError as e:
logging.error(str(e))
sys.exit(2)
for iface, family_config in ifaces.items():
for family, config in family_config.items():
logging.debug('Converting %s family %s %s', iface, family, config)
if iface not in auto_ifaces:
logging.error('%s: non-automatic interfaces are not supported', iface)
sys.exit(2)
if config['method'] == 'loopback':
# both systemd and modern ifupdown set up lo automatically
logging.debug('Ignoring loopback interface %s', iface)
elif config['method'] == 'dhcp':
c = netplan_config.setdefault('network', {}).setdefault('ethernets', {}).setdefault(iface, {})
self.parse_dns_options(config['options'], c)
self.parse_hwaddress(iface, config['options'], c)
if config['options']:
logging.error('%s: option(s) %s are not supported for dhcp method',
iface, ", ".join(config['options'].keys()))
sys.exit(2)
if family == 'inet':
c['dhcp4'] = True
else:
assert family == 'inet6'
c['dhcp6'] = True
elif config['method'] == 'static':
c = netplan_config.setdefault('network', {}).setdefault('ethernets', {}).setdefault(iface, {})
if 'addresses' not in c:
c['addresses'] = []
self.parse_dns_options(config['options'], c)
self.parse_mtu(iface, config['options'], c)
self.parse_hwaddress(iface, config['options'], c)
# ipv4
if family == 'inet':
# Already handled: mtu, hwaddress
# Supported: address netmask gateway
# Not supported yet: metric(?)
# No YAML support: pointopoint scope broadcast
supported_opts = set(['address', 'netmask', 'gateway'])
unsupported_opts = set(['broadcast', 'metric', 'pointopoint', 'scope'])
opts = set(config['options'].keys())
bad_opts = opts - supported_opts
if bad_opts:
for unsupported in bad_opts.intersection(unsupported_opts):
logging.error('%s: unsupported %s option "%s"', iface, family, unsupported)
sys.exit(2)
for unknown in bad_opts - unsupported_opts:
logging.error('%s: unknown %s option "%s"', iface, family, unknown)
sys.exit(2)
# the address may contain a /prefix suffix, or
# the netmask property may be used. It's not clear
# what happens if both are supplied.
if 'address' not in config['options']:
logging.error('%s: no address supplied in static method', iface)
sys.exit(2)
if '/' in config['options']['address']:
addr_spec = config['options']['address'].split('/')[0]
net_spec = config['options']['address']
else:
if 'netmask' not in config['options']:
logging.error('%s: address does not specify prefix length, and netmask not specified',
iface)
sys.exit(2)
addr_spec = config['options']['address']
net_spec = config['options']['address'] + '/' + config['options']['netmask']
try:
ipaddr = ipaddress.IPv4Address(addr_spec)
except ipaddress.AddressValueError as a:
logging.error('%s: error parsing "%s" as an IPv4 address: %s', iface, addr_spec, a)
sys.exit(2)
try:
ipnet = ipaddress.IPv4Network(net_spec, strict=False)
except ipaddress.NetmaskValueError as a:
logging.error('%s: error parsing "%s" as an IPv4 network: %s', iface, net_spec, a)
sys.exit(2)
c['addresses'] += [str(ipaddr) + '/' + str(ipnet.prefixlen)]
if 'gateway' in config['options']:
# validate?
c['gateway4'] = config['options']['gateway']
# ipv6
else:
assert family == 'inet6'
# Already handled: mtu, hwaddress
# supported: address netmask gateway
# partially supported: accept_ra (0/1 supported, 2 has no YAML rep)
# unsupported: metric(?)
# no YAML representation: media autoconf privext scope
# preferred-lifetime dad-attempts dad-interval
supported_opts = set(['address', 'netmask', 'gateway', 'accept_ra'])
unsupported_opts = set(['metric', 'media', 'autoconf', 'privext',
'scope', 'preferred-lifetime', 'dad-attempts', 'dad-interval'])
opts = set(config['options'].keys())
bad_opts = opts - supported_opts
if bad_opts:
for unsupported in bad_opts.intersection(unsupported_opts):
logging.error('%s: unsupported %s option "%s"', iface, family, unsupported)
sys.exit(2)
for unknown in bad_opts - unsupported_opts:
logging.error('%s: unknown %s option "%s"', iface, family, unknown)
sys.exit(2)
# the address may contain a /prefix suffix, or
# the netmask property may be used. It's not clear
# what happens if both are supplied.
if 'address' not in config['options']:
logging.error('%s: no address supplied in static method', iface)
sys.exit(2)
if '/' in config['options']['address']:
addr_spec = config['options']['address'].split('/')[0]
net_spec = config['options']['address']
else:
if 'netmask' not in config['options']:
logging.error('%s: address does not specify prefix length, and netmask not specified',
iface)
sys.exit(2)
addr_spec = config['options']['address']
net_spec = config['options']['address'] + '/' + config['options']['netmask']
try:
ipaddr = ipaddress.IPv6Address(addr_spec)
except ipaddress.AddressValueError as a:
logging.error('%s: error parsing "%s" as an IPv6 address: %s', iface, addr_spec, a)
sys.exit(2)
try:
ipnet = ipaddress.IPv6Network(net_spec, strict=False)
except ipaddress.NetmaskValueError as a:
logging.error('%s: error parsing "%s" as an IPv6 network: %s', iface, net_spec, a)
sys.exit(2)
c['addresses'] += [str(ipaddr) + '/' + str(ipnet.prefixlen)]
if 'gateway' in config['options']:
# validate?
c['gateway6'] = config['options']['gateway']
if 'accept_ra' in config['options']:
if config['options']['accept_ra'] == '0':
c['accept_ra'] = False
elif config['options']['accept_ra'] == '1':
c['accept_ra'] = True
elif config['options']['accept_ra'] == '2':
logging.error('%s: netplan does not support accept_ra=2', iface)
sys.exit(2)
else:
logging.error('%s: unexpected accept_ra value "%s"', iface,
config['options']['accept_ra'])
sys.exit(2)
else: # pragma nocover
# this should be unreachable
logging.error('%s: method %s is not supported', iface, config['method'])
sys.exit(2)
if_config = os.path.join(self.root_dir or '/', 'etc/network/interfaces')
if netplan_config:
netplan_config['network']['version'] = 2
netplan_yaml = yaml.dump(netplan_config)
if self.dry_run:
print(netplan_yaml)
else:
dest = os.path.join(self.root_dir or '/', 'etc/netplan/10-ifupdown.yaml')
try:
os.makedirs(os.path.dirname(dest))
except FileExistsError:
pass
try:
with open(dest, 'x') as f:
f.write(netplan_yaml)
except FileExistsError:
logging.error('%s already exists; remove it if you want to run the migration again', dest)
sys.exit(3)
logging.info('migration complete, wrote %s', dest)
else:
logging.info('ifupdown does not configure any interfaces, nothing to migrate')
if not self.dry_run:
logging.info('renaming %s to %s.netplan-converted', if_config, if_config)
os.rename(if_config, if_config + '.netplan-converted')
def _ifupdown_lines_from_file(self, rootdir, path):
'''Return normalized lines from ifupdown config
This resolves "source" and "source-directory" includes.
'''
def expand_source_arg(rootdir, curdir, line):
arg = line.split()[1]
if arg.startswith('/'):
return rootdir + arg
else:
return curdir + '/' + arg
lines = []
rootdir_len = len(rootdir) + 1
try:
with open(rootdir + '/' + path) as f:
logging.debug('reading %s', f.name)
for line in f:
# normalize, strip empty lines and comments
line = line.strip()
if not line or line.startswith('#'):
continue
if line.startswith('source-directory '):
valid_re = re.compile('^[a-zA-Z0-9_-]+$')
d = expand_source_arg(rootdir, os.path.dirname(f.name), line)
for f in os.listdir(d):
if valid_re.match(f):
lines += self._ifupdown_lines_from_file(rootdir, os.path.join(d[rootdir_len:], f))
elif line.startswith('source '):
for f in glob(expand_source_arg(rootdir, os.path.dirname(f.name), line)):
lines += self._ifupdown_lines_from_file(rootdir, f[rootdir_len:])
else:
lines.append(line)
except FileNotFoundError:
logging.debug('%s/%s does not exist, ignoring', rootdir, path)
return lines
def parse_ifupdown(self, rootdir='/'):
'''Parse ifupdown configuration.
Return (iface_name → family → {method, options}, auto_ifaces: set) tuple
on successful parsing, or a ValueError when encountering an invalid file or
ifupdown features which are not supported (such as "mapping").
options is itself a dictionary option_name → value.
'''
# expected number of fields for every possible keyword, excluding the keyword itself
fieldlen = {'auto': 1, 'allow-auto': 1, 'allow-hotplug': 1, 'mapping': 1, 'no-scripts': 1, 'iface': 3}
# read and normalize all lines from config, with resolving includes
lines = self._ifupdown_lines_from_file(rootdir, '/etc/network/interfaces')
ifaces = OrderedDict()
auto = set()
in_options = None # interface name if parsing options lines after iface stanza
in_family = None
# we now have resolved all includes and normalized lines
for line in lines:
fields = line.split()
try:
# does the line start with a known stanza field?
exp_len = fieldlen[fields[0]]
logging.debug('line fields %s (expected length: %i)', fields, exp_len)
in_options = None # stop option line parsing of iface stanza
in_family = None
except KeyError:
# no known stanza field, are we in an iface stanza and parsing options?
if in_options:
logging.debug('in_options %s, parsing as option: %s', in_options, line)
ifaces[in_options][in_family]['options'][fields[0]] = line.split(maxsplit=1)[1]
continue
else:
raise ValueError('Unknown stanza type %s' % fields[0])
# do we have the expected #parameters?
if len(fields) != exp_len + 1:
raise ValueError('Expected %i fields for stanza type %s but got %i' %
(exp_len, fields[0], len(fields) - 1))
# we have a valid stanza line now, handle them
if fields[0] in ('auto', 'allow-auto', 'allow-hotplug'):
auto.add(fields[1])
elif fields[0] == 'mapping':
raise ValueError('mapping stanza is not supported')
elif fields[0] == 'no-scripts':
pass # ignore these
elif fields[0] == 'iface':
if fields[2] not in ('inet', 'inet6'):
raise ValueError('Unknown address family %s' % fields[2])
if fields[3] not in ('loopback', 'static', 'dhcp'):
raise ValueError('Unsupported method %s' % fields[3])
in_options = fields[1]
in_family = fields[2]
ifaces.setdefault(fields[1], OrderedDict())[in_family] = {'method': fields[3], 'options': {}}
else:
raise NotImplementedError('stanza type %s is not implemented' % fields[0]) # pragma nocover
logging.debug('final parsed interfaces: %s; auto ifaces: %s', ifaces, auto)
return (ifaces, auto)
netplan-0.107.1/netplan_cli/cli/commands/set.py 0000664 0000000 0000000 00000013112 14536110317 0021325 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2020-2023 Canonical, Ltd.
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan set command line'''
import tempfile
import re
import io
from ..utils import NetplanCommand
import netplan
FALLBACK_FILENAME = '70-netplan-set.yaml'
GLOBAL_KEYS = ['renderer', 'version']
class NetplanSet(NetplanCommand):
def __init__(self):
super().__init__(command_id='set',
description='Add new setting by specifying a dotted key=value pair like ethernets.eth0.dhcp4=true',
leaf=True)
def run(self):
self.parser.add_argument('key_value', type=str,
help='The nested key=value pair in dotted format. Value can be NULL to delete a key.')
self.parser.add_argument('--origin-hint', type=str,
help='Can be used to help choose a name for the overwrite YAML file. \
A .yaml suffix will be appended automatically.')
self.parser.add_argument('--root-dir', default='/',
help='Overwrite configuration files in this root directory instead of /')
self.func = self.command_set
self.parse_args()
self.run_command()
def command_set(self):
if self.origin_hint is not None and len(self.origin_hint) == 0:
raise Exception('Invalid/empty origin-hint')
if self.origin_hint:
filename = '.'.join((self.origin_hint, 'yaml'))
else:
filename = None
split = self.key_value.split('=', 1)
if len(split) != 2:
raise Exception('Invalid value specified')
key, value = split
if not key.startswith('network'):
key = '.'.join(('network', key))
# Split the string into a list on the dot separators, and unescape the remaining dots
yaml_path = [s.replace(r'\.', '.') for s in re.split(r'(?), have they been defined in
# pre-existing YAML files or not.
tmp.seek(0, io.SEEK_SET)
parser_output_file._load_nullable_overrides(tmp, constraint=filename)
# Parse the full YAML hierarchy and new patch, ignoring any
# nullable overrides (netdefs/globals) from pre-existing files
# and ignoring any nullable fields (settings to be deleted).
# This way we can avoid updates to certain netdefs/globals to be
# redirected into existing YAML files (defining those same
# stanzas) or ignored, but have them written out to the single
# output file.
# XXX: The origin file of each individual YAML setting/stanza
# should be tracked individually, to avoid this
# double-parsing workaround (LP: #2003727)
parser_output_file.load_yaml_hierarchy(self.root_dir)
tmp.seek(0, io.SEEK_SET)
parser_output_file.load_yaml(tmp)
# Import the partial parser state, ignoring duplicated netdefs
# from pre-existing YAML files, so we can force write the patch
# contents to the output file or update this file if exists.
state_output_file = netplan.State()
state_output_file.import_parser_results(parser_output_file)
state_output_file._write_yaml_file(filename, self.root_dir)
else:
state._update_yaml_hierarchy(FALLBACK_FILENAME, self.root_dir)
netplan-0.107.1/netplan_cli/cli/commands/sriov_rebind.py 0000664 0000000 0000000 00000003610 14536110317 0023221 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2022 Canonical, Ltd.
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan SR-IOV rebind command line'''
import logging
from .. import utils
from ..sriov import PCIDevice, bind_vfs, _get_pci_slot_name
class NetplanSriovRebind(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='rebind',
description='Rebind SR-IOV virtual functions of given physical functions to their driver',
leaf=True)
def run(self):
self.parser.add_argument('netdevs', type=str, nargs='*', default=[],
help='Space separated list of PF interface names')
self.func = self.command_rebind
self.parse_args()
self.run_command()
def command_rebind(self):
"""Bind virtual functions of SR-IOV devices to their corresponding driver after eswitch mode was changed"""
for iface in self.netdevs:
pci_addr = _get_pci_slot_name(iface)
pcidev = PCIDevice(pci_addr)
if not pcidev.is_pf:
logging.warning('{} does not seem to be a SR-IOV physical function'.format(iface))
continue
bound_vfs = bind_vfs(pcidev.vfs, pcidev.driver)
logging.info('{}: bound {} VFs'.format(pcidev, len(bound_vfs)))
netplan-0.107.1/netplan_cli/cli/commands/status.py 0000664 0000000 0000000 00000025067 14536110317 0022071 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2022 Canonical, Ltd.
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan status command line'''
import json
import logging
import re
import yaml
from .. import utils
from ..state import SystemConfigState, JSON
MATCH_TAGS = re.compile(r'\[([a-z0-9]+)\].*\[\/\1\]')
RICH_OUTPUT = False
try:
from rich.console import Console
from rich.highlighter import RegexHighlighter
from rich.theme import Theme
class NetplanHighlighter(RegexHighlighter):
base_style = 'netplan.'
highlights = [
r'(^|[\s\/])(?P\d+)([\s:]?\s|$)',
r'(?P(\"|\').+(\"|\'))',
]
RICH_OUTPUT = True
except ImportError: # pragma: nocover (we mock RICH_OUTPUT, ignore the logging)
logging.debug("python3-rich not found, falling back to plain output")
class NetplanStatus(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='status',
description='Query networking state of the running system',
leaf=True)
self.all = False
def run(self):
self.parser.add_argument('ifname', nargs='?', type=str, default=None,
help='Show only this interface')
self.parser.add_argument('-a', '--all', action='store_true',
help='Show all interface data (incl. inactive)')
self.parser.add_argument('-v', '--verbose', action='store_true',
help='Show extra information')
self.parser.add_argument('-f', '--format', default='tabular',
help='Output in machine readable `json` or `yaml` format')
self.func = self.command
self.parse_args()
self.run_command()
def plain_print(self, *args, **kwargs):
if len(args):
lst = list(args)
for tag in MATCH_TAGS.findall(lst[0]):
# remove matching opening and closing tag
lst[0] = lst[0].replace('[{}]'.format(tag), '')\
.replace('[/{}]'.format(tag), '')
return print(*lst, **kwargs)
return print(*args, **kwargs)
def pretty_print(self, data: JSON, total: int, _console_width=None) -> None:
if RICH_OUTPUT:
# TODO: Use a proper (subiquity?) color palette
theme = Theme({
'netplan.int': 'bold cyan',
'netplan.str': 'yellow',
'muted': 'grey62',
'online': 'green bold',
'offline': 'red bold',
'unknown': 'yellow bold',
'highlight': 'bold'
})
console = Console(highlighter=NetplanHighlighter(), theme=theme,
width=_console_width, emoji=False)
pprint = console.print
else:
pprint = self.plain_print
pad = '18'
global_state = data.get('netplan-global-state', {})
interfaces = [(key, data[key]) for key in data if key != 'netplan-global-state']
# Global state
pprint(('{title:>'+pad+'} {value}').format(
title='Online state:',
value='[online]online[/online]' if global_state.get('online', False) else '[offline]offline[/offline]',
))
ns = global_state.get('nameservers', {})
dns_addr: list = ns.get('addresses', [])
dns_mode: str = ns.get('mode')
dns_search: list = ns.get('search', [])
if dns_addr:
for i, val in enumerate(dns_addr):
pprint(('{title:>'+pad+'} {value}[muted]{mode}[/muted]').format(
title='DNS Addresses:' if i == 0 else '',
value=val,
mode=' ({})'.format(dns_mode) if dns_mode else '',
))
if dns_search:
for i, val in enumerate(dns_search):
pprint(('{title:>'+pad+'} {value}').format(
title='DNS Search:' if i == 0 else '',
value=val,
))
pprint()
# Per interface
for (ifname, data) in interfaces:
state = data.get('operstate', 'UNKNOWN') + '/' + data.get('adminstate', 'UNKNOWN')
scolor = 'unknown'
if state == 'UP/UP':
state = 'UP'
scolor = 'online'
elif state == 'DOWN/DOWN':
state = 'DOWN'
scolor = 'offline'
full_type = data.get('type', 'other')
ssid = data.get('ssid')
tunnel_mode = data.get('tunnel_mode')
if full_type == 'wifi' and ssid:
full_type += ('/"' + ssid + '"')
elif full_type == 'tunnel' and tunnel_mode:
full_type += ('/' + tunnel_mode)
pprint('[{col}]●[/{col}] {idx:>2}: {name} {type} [{col}]{state}[/{col}] ({backend}{netdef})'.format(
col=scolor,
idx=data.get('index', '?'),
name=ifname,
type=full_type,
state=state,
backend=data.get('backend', 'unmanaged'),
netdef=': [highlight]{}[/highlight]'.format(data.get('id')) if data.get('id') else ''
))
if data.get('macaddress'):
pprint(('{title:>'+pad+'} {mac}[muted]{vendor}[/muted]').format(
title='MAC Address:',
mac=data.get('macaddress', ''),
vendor=' ({})'.format(data.get('vendor', '')) if data.get('vendor') else '',
))
lst: list = data.get('addresses', [])
if lst:
for i, obj in enumerate(lst):
ip, extra = list(obj.items())[0] # get first (any only) address
flags = []
if extra.get('flags'): # flags
flags = extra.get('flags', [])
highlight_start = ''
highlight_end = ''
if not flags or 'dhcp' in flags:
highlight_start = '[highlight]'
highlight_end = '[/highlight]'
pprint(('{title:>'+pad+'} {start}{ip}/{prefix}{end}[muted]{extra}[/muted]').format(
title='Addresses:' if i == 0 else '',
ip=ip,
prefix=extra.get('prefix', ''),
extra=' ('+', '.join(flags)+')' if flags else '',
start=highlight_start,
end=highlight_end,
))
lst = data.get('dns_addresses', [])
if lst:
for i, val in enumerate(lst):
pprint(('{title:>'+pad+'} {value}').format(
title='DNS Addresses:' if i == 0 else '',
value=val,
))
lst = data.get('dns_search', [])
if lst:
for i, val in enumerate(lst):
pprint(('{title:>'+pad+'} {value}').format(
title='DNS Search:' if i == 0 else '',
value=val,
))
lst = data.get('routes', [])
if lst:
for i, obj in enumerate(lst):
if not self.verbose and obj.get('table') != 'main':
continue
default_start = ''
default_end = ''
if obj['to'] == 'default':
default_start = '[highlight]'
default_end = '[/highlight]'
via = ''
if 'via' in obj:
via = ' via ' + obj['via']
src = ''
if 'from' in obj:
src = ' from ' + obj['from']
metric = ''
if 'metric' in obj:
metric = ' metric ' + str(obj['metric'])
table = ''
if self.verbose and 'table' in obj:
table = ' table ' + str(obj['table'])
extra = []
if 'protocol' in obj and obj['protocol'] != 'kernel':
proto = obj['protocol']
extra.append(proto)
if 'scope' in obj and obj['scope'] != 'global':
scope = obj['scope']
extra.append(scope)
if 'type' in obj and obj['type'] != 'unicast':
type = obj['type']
extra.append(type)
pprint(('{title:>'+pad+'} {start}{to}{via}{src}{metric}{table}{end}[muted]{extra}[/muted]').format(
title='Routes:' if i == 0 else '',
to=obj['to'],
via=via,
src=src,
metric=metric,
table=table,
extra=' ('+', '.join(extra)+')' if extra else '',
start=default_start,
end=default_end))
val = data.get('activation_mode')
if val:
pprint(('{title:>'+pad+'} {value}').format(
title='Activation Mode:',
value=val,
))
pprint()
hidden = total - len(interfaces)
if (hidden > 0):
pprint('{} inactive interfaces hidden. Use "--all" to show all.'.format(hidden))
def command(self):
state_data = SystemConfigState(self.ifname, self.all)
# Output data in requested format
output_format = self.format.lower()
if output_format == 'json': # structural JSON output
print(json.dumps(state_data.get_data()))
elif output_format == 'yaml': # stuctural YAML output
print(yaml.dump(state_data.get_data()))
else: # pretty print, human readable output
self.pretty_print(state_data.get_data(), state_data.number_of_interfaces)
netplan-0.107.1/netplan_cli/cli/commands/try_command.py 0000664 0000000 0000000 00000017502 14536110317 0023055 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan try command line'''
import logging
import os
import time
import shutil
import signal
import sys
import tempfile
from ...configmanager import ConfigManager
from .. import utils
from .apply import NetplanApply
from ... import terminal
# Keep a timeout long enough to allow the network to converge, 60 seconds may
# be slightly short given some complex configs, i.e. if STP must reconverge.
DEFAULT_INPUT_TIMEOUT = 120
class NetplanTry(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='try',
description='Try to apply a new netplan config to running '
'system, with automatic rollback',
leaf=True)
self.configuration_changed = False
self.new_interfaces = None
self.config_file = None
self._config_manager = None
self.t_settings = None
self.t = None
self._rootdir = os.environ.get('DBUS_TEST_NETPLAN_ROOT', '/')
self._netplan_try_stamp = os.path.join(self._rootdir, 'run', 'netplan', 'netplan-try.ready')
@property
def config_manager(self): # pragma: nocover (called by later commands)
if not self._config_manager:
self._config_manager = ConfigManager(prefix=self._rootdir)
return self._config_manager
def clear_ready_stamp(self):
if os.path.isfile(self._netplan_try_stamp):
os.remove(self._netplan_try_stamp)
return True
return False
def touch_ready_stamp(self):
os.makedirs(self._rootdir + '/run/netplan', mode=0o700, exist_ok=True)
open(self._netplan_try_stamp, 'w').close()
def run(self): # pragma: nocover (requires user input)
self.parser.add_argument('--config-file',
help='Apply the config file in argument in addition to current configuration.')
self.parser.add_argument('--timeout',
type=int, default=DEFAULT_INPUT_TIMEOUT,
help="Maximum number of seconds to wait for the user's confirmation")
self.parser.add_argument('--state',
help='Directory containing previous YAML configuration')
self.func = self.command_try
self.parse_args()
self.run_command()
def command_try(self): # pragma: nocover (requires user input)
if not self.is_revertable():
sys.exit(os.EX_CONFIG)
try:
fd = sys.stdin.fileno()
self.t = terminal.Terminal(fd)
self.t.save(self.t_settings)
# we really don't want to be interrupted while doing backup/revert operations
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGUSR1, self._signal_handler)
self.backup()
self.setup()
NetplanApply().command_apply(run_generate=True, sync=True, exit_on_error=False, state_dir=self.state)
# Touch stamp file, it is the signal (for netplan-dbus) that we're
# ready to accept any Accept/Reject input (like SIGUSR1 or SIGTERM)
self.touch_ready_stamp()
self.t.get_confirmation_input(timeout=self.timeout)
except terminal.InputRejected:
print("\nReverting.")
self.revert()
except terminal.InputAccepted:
print("\nConfiguration accepted.")
except Exception as e:
print("\nAn error occurred: %s" % e)
print("\nReverting.")
self.revert()
finally:
if self.t:
self.t.reset(self.t_settings)
self.cleanup()
self.clear_ready_stamp()
def backup(self): # pragma: nocover (requires user input)
backup_config_dir = False
if self.config_file:
backup_config_dir = True
self.config_manager.backup(backup_config_dir=backup_config_dir)
def setup(self): # pragma: nocover (requires user input)
if self.config_file:
dest_dir = os.path.join("/", "etc", "netplan")
dest_name = os.path.basename(self.config_file).rstrip('.yaml')
dest_suffix = time.time()
dest_path = os.path.join(dest_dir, "{}.{}.yaml".format(dest_name, dest_suffix))
self.config_manager.add({self.config_file: dest_path})
self.configuration_changed = True
def revert(self): # pragma: nocover (requires user input)
# backup the state we just tried to apply
tempdir = tempfile.mkdtemp()
confdir = os.path.join(tempdir, 'etc', 'netplan')
os.makedirs(confdir)
shutil.copytree('/etc/netplan', confdir, dirs_exist_ok=True)
# restore previous state
self.config_manager.revert()
NetplanApply().command_apply(run_generate=False, sync=True, exit_on_error=False, state_dir=tempdir)
# clear the backup
shutil.rmtree(tempdir)
def cleanup(self): # pragma: nocover (requires user input)
self.config_manager.cleanup()
def is_revertable(self):
'''
Check if the configuration is revertable, if it doesn't contain bits
that we know are likely to render the system unstable if we apply it,
or if we revert.
Returns True if the parsed config is "revertable", meaning that we
can actually rely on backends to re-apply /all/ of the relevant
configuration to interfaces when their config changes.
Returns False if the parsed config contains options that are known
to not cleanly revert via the backend.
'''
extra_config = []
if self.config_file:
extra_config.append(self.config_file)
np_state = None
try:
np_state = self.config_manager.parse(extra_config=extra_config)
except utils.config_errors as e:
logging.error(e)
sys.exit(os.EX_CONFIG)
revert_unsupported = []
# Bridges and bonds are special. They typically include (or could include)
# more than one device in them, and they can be set with special parameters
# to tweak their behavior, which are really hard to "revert", especially
# as systemd-networkd doesn't necessarily touch them when config changes.
multi_iface = {} # dict[str, netplan.NetDefinition]
multi_iface.update(np_state.bridges)
multi_iface.update(np_state.bonds)
for itf in multi_iface.values():
if not itf._is_trivial_compound_itf:
reason = "reverting custom parameters for bridges and bonds is not supported"
revert_unsupported.append((itf.id, reason))
if revert_unsupported:
for ifname, reason in revert_unsupported:
print("{}: {}".format(ifname, reason))
print("\nPlease carefully review the configuration and use 'netplan apply' directly.")
return False
return True
def _signal_handler(self, sig, frame): # pragma: nocover (requires user input)
if sig == signal.SIGUSR1:
raise terminal.InputAccepted()
else:
if self.configuration_changed:
raise terminal.InputRejected()
netplan-0.107.1/netplan_cli/cli/core.py 0000664 0000000 0000000 00000004135 14536110317 0017666 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Martin Pitt
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan command line'''
import logging
import os
from . import utils
from netplan import NetplanException, NetplanValidationException, NetplanParserException
FALLBACK_PATH = '/usr/bin:/snap/bin'
class Netplan(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='',
description='Network configuration in YAML',
leaf=False)
os.environ.update({
'LC_ALL': 'C',
'PATH': os.getenv('PATH', FALLBACK_PATH)})
def parse_args(self):
from . import commands as cli_commands
self._import_subcommands(cli_commands)
super().parse_args()
def main(self):
self.parse_args()
if self.debug:
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(message)s')
os.environ['G_MESSAGES_DEBUG'] = 'all'
else:
logging.basicConfig(level=logging.INFO, format='%(message)s')
try:
self.run_command()
except NetplanParserException as e:
message = f'{e.filename}:{e.line}:{e.column}: {e}'
logging.warning(f'Command failed: {message}')
except NetplanValidationException as e:
logging.warning(f'Command failed: {e.filename}: {e}')
except NetplanException as e:
logging.warning(f'Command failed: {e}')
netplan-0.107.1/netplan_cli/cli/ovs.py 0000664 0000000 0000000 00000020225 14536110317 0017543 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2020 Canonical, Ltd.
# Author: Lukas 'slyon' Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
import logging
import os
import subprocess
import re
from .utils import systemctl_is_active
OPENVSWITCH_OVS_VSCTL = '/usr/bin/ovs-vsctl'
OPENVSWITCH_OVSDB_SERVER_UNIT = 'ovsdb-server.service'
# Defaults for non-optional settings, as defined here:
# http://www.openvswitch.org/ovs-vswitchd.conf.db.5.pdf
DEFAULTS = {
# Mandatory columns:
'mcast_snooping_enable': 'false',
'rstp_enable': 'false',
}
GLOBALS = {
# Global commands:
'set-ssl': ('del-ssl', 'get-ssl'),
'set-fail-mode': ('del-fail-mode', 'get-fail-mode'),
'set-controller': ('del-controller', 'get-controller'),
}
class OvsDbServerNotRunning(Exception):
pass
def _del_col(type, iface, column, value):
"""Cleanup values from a column (i.e. "column=value")"""
default = DEFAULTS.get(column)
if default is None:
# removes the exact value only if it was set by netplan
subprocess.check_call([OPENVSWITCH_OVS_VSCTL, 'remove', type, iface, column, value])
elif default and default != value:
# reset to default, if its not the default already
subprocess.check_call([OPENVSWITCH_OVS_VSCTL, 'set', type, iface, '%s=%s' % (column, default)])
def _del_dict(type, iface, column, key, value):
"""Cleanup values from a dictionary (i.e. "column:key=value")"""
# removes the exact value only if it was set by netplan
subprocess.check_call([OPENVSWITCH_OVS_VSCTL, 'remove', type, iface, column, key, _escape_colon(value)])
# for ovsdb remove: column key's value can not contain bare ':', need to escape with '\'
def _escape_colon(literal):
return re.sub(r'([^\\]):', r'\g<1>\:', literal)
def _del_global(type, iface, key, value):
"""Cleanup commands from the global namespace"""
del_cmd, get_cmd = GLOBALS.get(key, (None, None))
if del_cmd == 'del-ssl':
iface = None
if del_cmd:
args_get = [OPENVSWITCH_OVS_VSCTL, get_cmd]
args_del = [OPENVSWITCH_OVS_VSCTL, del_cmd]
if iface:
args_get.append(iface)
args_del.append(iface)
# Check the current value of a global command and compare it to the tag-value, e.g.:
# * get-ssl: netplan/global/set-ssl=/private/key.pem,/another/cert.pem,/some/ca-cert.pem
# Private key: /private/key.pem
# Certificate: /another/cert.pem
# CA Certificate: /some/ca-cert.pem
# Bootstrap: false
# * get-fail-mode: netplan/global/set-fail-mode=secure
# secure
# * get-controller: netplan/global/set-controller=tcp:127.0.0.1:1337,unix:/some/socket
# tcp:127.0.0.1:1337
# unix:/some/socket
out = subprocess.check_output(args_get, text=True)
# Clean it only if the exact same value(s) were set by netplan.
# Don't touch it if other values were set by another integration.
if all(item in out for item in value.split(',')):
subprocess.check_call(args_del)
else:
raise Exception('Reset command unknown for:', key)
def clear_setting(type, iface, setting, value):
"""Check if this setting is in a dict or a colum and delete accordingly"""
split = setting.split('/', 2)
col = split[1]
if col == 'global' and len(split) > 2:
_del_global(type, iface, split[2], value)
elif len(split) > 2:
_del_dict(type, iface, split[1], split[2], value)
else:
_del_col(type, iface, split[1], value)
# Cleanup the tag itself (i.e. "netplan/column[/key]")
subprocess.check_call([OPENVSWITCH_OVS_VSCTL, 'remove', type, iface, 'external-ids', setting])
def is_ovs_interface(iface, np_interface_dict):
assert isinstance(np_interface_dict, dict)
np_def = np_interface_dict.get(iface, None)
return np_def and np_def.backend == 'OpenVSwitch'
def apply_ovs_cleanup(config_manager, ovs_old, ovs_current): # pragma: nocover (covered in autopkgtest)
"""
Query OpenVSwitch state through 'ovs-vsctl' and filter for netplan=true
tagged ports/bonds and bridges. Delete interfaces which are not defined
in the current configuration.
Also filter for individual settings tagged netplan/[/ 0:
subprocess.check_call([OPENVSWITCH_OVS_VSCTL, '--if-exists', 'del-bond-iface', iface])
else:
subprocess.check_call([OPENVSWITCH_OVS_VSCTL, '--if-exists', t[1], iface])
# Step 2: Clean up the settings of the remaining interfaces
for t in ('Port', 'Bridge', 'Interface', 'Open_vSwitch', 'Controller'):
cols = 'name,external-ids'
if t == 'Open_vSwitch':
cols = 'external-ids'
elif t == 'Controller':
cols = '_uuid,external-ids' # handle _uuid as if it would be the iface 'name'
out = subprocess.check_output([OPENVSWITCH_OVS_VSCTL, '--columns=%s' % cols,
'-f', 'csv', '-d', 'bare', '--no-headings', 'list', t],
text=True)
for line in out.splitlines():
if 'netplan/' in line:
iface = '.'
extids = line
if t != 'Open_vSwitch':
iface, extids = line.split(',', 1)
# Check each line (interface) if it contains any netplan tagged settings, e.g.:
# ovs0,"iface-id=myhostname netplan=true netplan/external-ids/iface-id=myhostname"
# ovs1,"netplan=true netplan/global/set-fail-mode=standalone netplan/mcast_snooping_enable=false"
for entry in extids.strip('"').split(' '):
if entry.startswith('netplan/') and '=' in entry:
setting, val = entry.split('=', 1)
clear_setting(t, iface, setting, val)
# Show the warning only if we are or have been working with OVS definitions
elif ovs_old or ovs_current:
logging.warning('ovs-vsctl is missing, cannot tear down old OpenVSwitch interfaces')
netplan-0.107.1/netplan_cli/cli/sriov.py 0000664 0000000 0000000 00000043314 14536110317 0020102 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2020-2022 Canonical, Ltd.
# Author: Łukasz 'sil2100' Zemczak
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
import logging
import os
import subprocess
import typing
from collections import defaultdict
from . import utils
from ..configmanager import ConfigurationError
import netplan
import netifaces
# PCIDevice class originates from mlnx_switchdev_mode/sriovify.py
# Copyright 2019 Canonical Ltd, Apache License, Version 2.0
# https://github.com/openstack-charmers/mlnx-switchdev-mode
class PCIDevice(object):
"""Helper class for interaction with a PCI device"""
def __init__(self, pci_addr: str):
"""Initialise a new PCI device handler
:param pci_addr: PCI address of device
:type: str
"""
self.pci_addr = pci_addr
@property
def sys(self) -> str:
"""sysfs path (can be overridden for testing)
:return: full path to /sys filesystem
:rtype: str
"""
return "/sys"
@property
def path(self) -> str:
"""/sys path for PCI device
:return: full path to PCI device in /sys filesystem
:rtype: str
"""
return os.path.join(self.sys, "bus/pci/devices", self.pci_addr)
def subpath(self, subpath: str) -> str:
"""/sys subpath helper for PCI device
:param subpath: subpath to construct path for
:type: str
:return: self.path + subpath
:rtype: str
"""
return os.path.join(self.path, subpath)
@property
def driver(self) -> str:
"""Kernel driver for PCI device
:return: kernel driver in use for device
:rtype: str
"""
driver = ''
if os.path.exists(self.subpath("driver")):
driver = os.path.basename(os.readlink(self.subpath("driver")))
return driver
@property
def bound(self) -> bool:
"""Determine if device is bound to a kernel driver
:return: whether device is bound to a kernel driver
:rtype: bool
"""
return os.path.exists(self.subpath("driver"))
@property
def is_pf(self) -> bool:
"""Determine if device is a SR-IOV Physical Function
:return: whether device is a PF
:rtype: bool
"""
return os.path.exists(self.subpath("sriov_numvfs"))
@property
def is_vf(self) -> bool:
"""Determine if device is a SR-IOV Virtual Function
:return: whether device is a VF
:rtype: bool
"""
return os.path.exists(self.subpath("physfn"))
@property
def vf_addrs(self) -> list:
"""List Virtual Function addresses associated with a Physical Function
:return: List of PCI addresses of Virtual Functions
:rtype: list[str]
"""
vf_addrs = []
i = 0
while True:
try:
vf_addrs.append(
os.path.basename(
os.readlink(self.subpath("virtfn{}".format(i)))
)
)
except FileNotFoundError:
break
i += 1
return vf_addrs
@property
def vfs(self) -> list:
"""List Virtual Function associated with a Physical Function
:return: List of PCI devices of Virtual Functions
:rtype: list[PCIDevice]
"""
return [PCIDevice(addr) for addr in self.vf_addrs]
def devlink_set(self, obj_name: str, prop: str, value: str):
"""Set devlink options for the PCI device
:param obj_name: devlink object to set options on
:type: str
:param prop: property to set
:type: str
:param value: value to set for property
:type: str
"""
subprocess.check_call(
[
"/sbin/devlink",
"dev",
obj_name,
"set",
"pci/{}".format(self.pci_addr),
prop,
value,
]
)
def __str__(self) -> str:
"""String represenation of object
:return: PCI address of string
:rtype: str
"""
return self.pci_addr
def bind_vfs(vfs: typing.Iterable[PCIDevice], driver):
"""Bind unbound VFs to driver."""
bound_vfs = []
for vf in vfs:
if not vf.bound:
with open("/sys/bus/pci/drivers/{}/bind".format(driver), "wt") as f:
f.write(vf.pci_addr)
bound_vfs.append(vf)
return bound_vfs
def unbind_vfs(vfs: typing.Iterable[PCIDevice], driver) -> typing.Iterable[PCIDevice]:
"""Unbind bound VFs from driver."""
unbound_vfs = []
for vf in vfs:
if vf.bound:
with open("/sys/bus/pci/drivers/{}/unbind".format(driver), "wt") as f:
f.write(vf.pci_addr)
unbound_vfs.append(vf)
return unbound_vfs
def _get_target_interface(interfaces, np_state, pf_link, pfs):
if pf_link not in pfs:
# handle the match: syntax, get the actual device name
pf_dev = np_state[pf_link]
if pf_dev._has_match:
# now here it's a bit tricky
set_name = pf_dev.set_name
if set_name and set_name in interfaces:
# if we had a match: stanza and set-name: this means we should
# assume that, if found, the interface has already been
# renamed - use the new name
pfs[pf_link] = set_name
else:
for interface in interfaces:
if not pf_dev._match_interface(
iface_name=interface,
iface_driver=utils.get_interface_driver_name(interface),
iface_mac=utils.get_interface_macaddress(interface)):
continue
# we have a matching PF
# store the matching interface in the dictionary of
# active PFs, but error out if we matched more than one
if pf_link in pfs:
raise ConfigurationError('matched more than one interface for a PF device: %s' % pf_link)
pfs[pf_link] = interface
else:
# no match field, assume entry name is the interface name
if pf_link in interfaces:
pfs[pf_link] = pf_link
return pfs.get(pf_link, None)
def _get_pci_slot_name(netdev):
"""
Read PCI slot name for given interface name
"""
uevent_path = os.path.join('/sys/class/net', netdev, 'device/uevent')
try:
with open(uevent_path) as f:
pci_slot_name = None
for line in f.readlines():
line = line.strip()
if line.startswith('PCI_SLOT_NAME='):
pci_slot_name = line.split('=', 2)[1]
return pci_slot_name
except IOError as e:
raise RuntimeError('failed parsing PCI slot name for %s: %s' % (netdev, str(e)))
def get_vf_count_and_functions(interfaces, np_state,
vf_counts, vfs, pfs):
"""
Go through the list of netplan ethernet devices and identify which are
PFs and VFs, matching the former with actual networking interfaces.
Count how many VFs each PF will need.
"""
for nid, netdef in np_state.ethernets.items():
if netdef.links.get('sriov') and _get_target_interface(interfaces, np_state, netdef.links.get('sriov').id, pfs):
vfs[nid] = None
try:
count = netdef._vf_count
except netplan.NetplanException as e:
raise ConfigurationError(str(e))
if count == 0:
continue
pf = _get_target_interface(interfaces, np_state, nid, pfs)
if pf:
vf_counts[pf] = count
def set_numvfs_for_pf(pf, vf_count):
"""
Allocate the required number of VFs for the selected PF.
"""
if vf_count > 256:
raise ConfigurationError(
'cannot allocate more VFs for PF %s than the SR-IOV maximum: %s > 256' % (pf, vf_count))
devdir = os.path.join('/sys/class/net', pf, 'device')
numvfs_path = os.path.join(devdir, 'sriov_numvfs')
totalvfs_path = os.path.join(devdir, 'sriov_totalvfs')
try:
with open(totalvfs_path) as f:
vf_max = int(f.read().strip())
except IOError as e:
raise RuntimeError('failed parsing sriov_totalvfs for %s: %s' % (pf, str(e)))
except ValueError:
raise RuntimeError('invalid sriov_totalvfs value for %s' % pf)
if vf_count > vf_max:
raise ConfigurationError(
'cannot allocate more VFs for PF %s than supported: %s > %s (sriov_totalvfs)' % (pf, vf_count, vf_max))
try:
with open(numvfs_path, 'w') as f:
f.write(str(vf_count))
except IOError as e:
bail = True
if e.errno == 16: # device or resource busy
logging.warning('device or resource busy while setting sriov_numvfs for %s, trying workaround' % pf)
try:
# doing this in two open/close sequences so that
# it's as close to writing via shell as possible
with open(numvfs_path, 'w') as f:
f.write('0')
with open(numvfs_path, 'w') as f:
f.write(str(vf_count))
except IOError as e_inner:
e = e_inner
else:
bail = False
if bail:
raise RuntimeError('failed setting sriov_numvfs to %s for %s: %s' % (vf_count, pf, str(e)))
return True
def perform_hardware_specific_quirks(pf):
"""
Perform any hardware-specific quirks for the given SR-IOV device to make
sure all the VF-count changes are applied.
"""
devdir = os.path.join('/sys/class/net', pf, 'device')
try:
with open(os.path.join(devdir, 'vendor')) as f:
device_id = f.read().strip()[2:]
with open(os.path.join(devdir, 'device')) as f:
vendor_id = f.read().strip()[2:]
except IOError as e:
raise RuntimeError('could not determine vendor and device ID of %s: %s' % (pf, str(e)))
combined_id = ':'.join([vendor_id, device_id])
quirk_devices = () # TODO: add entries to the list
if combined_id in quirk_devices: # pragma: nocover (empty quirk_devices)
# some devices need special handling, so this is the place
# Currently this part is empty, but has been added as a preemptive
# measure, as apparently a lot of SR-IOV cards have issues with
# dynamically allocating VFs. Some cards seem to require a full
# kernel module reload cycle after changing the sriov_numvfs value
# for the changes to come into effect.
# Any identified card/vendor can then be special-cased here, if
# needed.
pass
def apply_vlan_filter_for_vf(pf, vf, vlan_name, vlan_id, prefix='/'):
"""
Apply the hardware VLAN filtering for the selected VF.
"""
# this is more complicated, because to do this, we actually need to have
# the vf index - just knowing the vf interface name is not enough
vf_index = None
# the prefix argument is here only for unit testing purposes
vf_devdir = os.path.join(prefix, 'sys/class/net', vf, 'device')
vf_dev_id = os.path.basename(os.readlink(vf_devdir))
pf_devdir = os.path.join(prefix, 'sys/class/net', pf, 'device')
for f in os.listdir(pf_devdir):
if 'virtfn' in f:
dev_path = os.path.join(pf_devdir, f)
dev_id = os.path.basename(os.readlink(dev_path))
if dev_id == vf_dev_id:
vf_index = f[6:]
break
if not vf_index:
raise RuntimeError(
'could not determine the VF index for %s while configuring vlan %s' % (vf, vlan_name))
# now, create the VLAN filter
# TODO: would be best if we did this directl via python, without calling
# the iproute tooling
try:
subprocess.check_call(['ip', 'link', 'set',
'dev', pf,
'vf', vf_index,
'vlan', str(vlan_id)],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError:
raise RuntimeError(
'failed setting SR-IOV VLAN filter for vlan %s (ip link set command failed)' % vlan_name)
def apply_sriov_config(config_manager, rootdir='/'):
"""
Go through all interfaces, identify which ones are SR-IOV VFs, create
them and perform all other necessary setup.
"""
parser = netplan.Parser()
parser.load_yaml_hierarchy(rootdir)
np_state = netplan.State()
np_state.import_parser_results(parser)
config_manager.parse()
interfaces = netifaces.interfaces()
np_state = config_manager.np_state
# for sr-iov devices, we identify VFs by them having a link: field
# pointing to an PF. So let's browse through all ethernet devices,
# find all that are VFs and count how many of those are linked to
# particular PFs, as we need to then set the numvfs for each.
vf_counts = defaultdict(int)
# we also store all matches between VF/PF netplan entry names and
# interface that they're currently matching to
vfs = {}
pfs = {}
get_vf_count_and_functions(
interfaces, np_state, vf_counts, vfs, pfs)
# setup the required number of VFs per PF
# at the same time store which PFs got changed in case the NICs
# require some special quirks for the VF number to change
vf_count_changed = []
if vf_counts:
for pf, vf_count in vf_counts.items():
if not set_numvfs_for_pf(pf, vf_count):
continue
vf_count_changed.append(pf)
if vf_count_changed:
# some cards need special treatment when we want to change the
# number of enabled VFs
for pf in vf_count_changed:
perform_hardware_specific_quirks(pf)
# also, since the VF number changed, the interfaces list also
# changed, so we need to refresh it
interfaces = netifaces.interfaces()
# now in theory we should have all the new VFs set up and existing;
# this is needed because we will have to now match the defined VF
# entries to existing interfaces, otherwise we won't be able to set
# filtered VLANs for those.
# XXX: does matching those even make sense?
for vf in vfs:
netdef = np_state[vf]
if netdef._has_match:
# right now we only match by name, as I don't think matching per
# driver and/or macaddress makes sense
# TODO: print warning if other matches are provided
for interface in interfaces:
if netdef._match_interface(iface_name=interface):
if vf in vfs and vfs[vf]:
raise ConfigurationError('matched more than one interface for a VF device: %s' % vf)
vfs[vf] = interface
else:
if vf in interfaces:
vfs[vf] = vf
# Walk the SR-IOV PFs and check if we need to change the eswitch mode
for netdef_id, iface in pfs.items():
netdef = np_state[netdef_id]
eswitch_mode = netdef._embedded_switch_mode
if eswitch_mode in ['switchdev', 'legacy']:
pci_addr = _get_pci_slot_name(iface)
pcidev = PCIDevice(pci_addr)
if pcidev.is_pf:
logging.debug("Found VFs of {}: {}".format(pcidev, pcidev.vf_addrs))
if pcidev.vfs:
rebind_delayed = netdef._delay_virtual_functions_rebind
try:
unbind_vfs(pcidev.vfs, pcidev.driver)
pcidev.devlink_set('eswitch', 'mode', eswitch_mode)
finally:
if not rebind_delayed:
bind_vfs(pcidev.vfs, pcidev.driver)
filtered_vlans_set = set()
for vlan, netdef in np_state.vlans.items():
# there is a special sriov vlan renderer that one can use to mark
# a selected vlan to be done in hardware (VLAN filtering)
if netdef._has_sriov_vlan_filter:
# this only works for SR-IOV VF interfaces
link = netdef.links.get('vlan')
vlan_id = netdef._vlan_id
vf = vfs.get(link.id)
if not vf:
# it is possible this is not an error, for instance when
# the configuration has been defined 'for the future'
# XXX: but maybe we should error out here as well?
logging.warning(
'SR-IOV vlan defined for %s but link %s is either not a VF or has no matches' % (vlan, link.id))
continue
# get the parent pf interface
# first we fetch the related vf netplan entry
# and finally, get the matched pf interface
pf = pfs.get(link.links.get('sriov').id)
if vf in filtered_vlans_set:
raise ConfigurationError(
'interface %s for netplan device %s (%s) already has an SR-IOV vlan defined' % (vf, link.id, vlan))
# TODO: make sure that we don't apply the filter twice
apply_vlan_filter_for_vf(pf, vf, vlan, vlan_id)
filtered_vlans_set.add(vf)
netplan-0.107.1/netplan_cli/cli/state.py 0000664 0000000 0000000 00000047507 14536110317 0020070 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2023 Canonical, Ltd.
# Authors: Lukas Märdian
# Danilo Egea Gondolfo
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
import ipaddress
import json
import logging
import re
import socket
import subprocess
import sys
from io import StringIO
from typing import Dict, List, Type, Union
import yaml
import dbus
import netplan
from . import utils
JSON = Union[Dict[str, 'JSON'], List['JSON'], int, str, float, bool, Type[None]]
DEVICE_TYPES = {
'bond': 'bond',
'bridge': 'bridge',
'ether': 'ethernet',
'ipgre': 'tunnel',
'ip6gre': 'tunnel',
'loopback': 'ethernet',
'sit': 'tunnel',
'tunnel': 'tunnel',
'tunnel6': 'tunnel',
'wireguard': 'tunnel',
'wlan': 'wifi',
'wwan': 'modem',
'vlan': 'vlan',
'vrf': 'vrf',
'vxlan': 'tunnel',
# Netplan netdef types
'wifis': 'wifi',
'ethernets': 'ethernet',
'bridges': 'bridge',
'bonds': 'bond',
'nm-devices': 'nm-device',
'dummy-devices': 'dummy',
'modems': 'modem',
'vlans': 'vlan',
'vrfs': 'vrf',
}
class Interface():
def __extract_mac(self, ip: dict) -> str:
'''
Extract the MAC address if it's set inside the JSON data and seems to
have the correct format. Return 'None' otherwise.
'''
if len(address := ip.get('address', '')) == 17: # 6 byte MAC (+5 colons)
return address.lower()
return None
def __init__(self, ip: dict, nd_data: JSON = [], nm_data: JSON = [],
resolved_data: tuple = (None, None), route_data: tuple = (None, None)):
self.idx: int = ip.get('ifindex', -1)
self.name: str = ip.get('ifname', 'unknown')
self.adminstate: str = 'UP' if 'UP' in ip.get('flags', []) else 'DOWN'
self.operstate: str = ip.get('operstate', 'unknown').upper()
self.macaddress: str = self.__extract_mac(ip)
# Filter networkd/NetworkManager data
nm_data = nm_data or [] # avoid 'None' value on systems without NM
self.nd: JSON = next((x for x in nd_data if x['Index'] == self.idx), None)
self.nm: JSON = next((x for x in nm_data if x['device'] == self.name), None)
# Filter resolved's DNS data
self.dns_addresses: list = None
if resolved_data[0]:
self.dns_addresses = []
for itr in resolved_data[0]:
if int(itr[0]) == int(self.idx):
ipfamily = itr[1]
dns = itr[2]
self.dns_addresses.append(socket.inet_ntop(ipfamily, b''.join([v.to_bytes(1, 'big') for v in dns])))
self.dns_search: list = None
if resolved_data[1]:
self.dns_search = []
for v in resolved_data[1]:
if int(v[0]) == int(self.idx):
self.dns_search.append(str(v[1]))
# Filter route data
_routes: list = []
self.routes: list = None
if route_data[0]:
_routes += route_data[0]
if route_data[1]:
_routes += route_data[1]
if _routes:
self.routes = []
for obj in _routes:
if obj.get('dev') == self.name:
elem = {'to': obj.get('dst')}
if val := obj.get('family'):
elem['family'] = val
if val := obj.get('gateway'):
elem['via'] = val
if val := obj.get('prefsrc'):
elem['from'] = val
if val := obj.get('metric'):
elem['metric'] = val
if val := obj.get('type'):
elem['type'] = val
if val := obj.get('scope'):
elem['scope'] = val
if val := obj.get('protocol'):
elem['protocol'] = val
if val := obj.get('table'):
elem['table'] = val
self.routes.append(elem)
self.addresses: list = None
if addr_info := ip.get('addr_info'):
self.addresses = []
for addr in addr_info:
flags: list = []
if ipaddress.ip_address(addr['local']).is_link_local:
flags.append('link')
if self.routes:
for route in self.routes:
if ('from' in route and
ipaddress.ip_address(route['from']) == ipaddress.ip_address(addr['local'])):
if route['protocol'] == 'dhcp':
flags.append('dhcp')
break
ip_addr = addr['local'].lower()
elem = {ip_addr: {'prefix': addr['prefixlen']}}
if flags:
elem[ip_addr]['flags'] = flags
self.addresses.append(elem)
self.iproute_type: str = None
if info_kind := ip.get('linkinfo', {}).get('info_kind'):
self.iproute_type = info_kind.strip()
# workaround: query some data which is not available via networkctl's JSON output
self._networkctl: str = self.query_networkctl(self.name) or ''
def query_nm_ssid(self, con_name: str) -> str:
ssid: str = None
try:
ssid = utils.nmcli_out(['--get-values', '802-11-wireless.ssid',
'con', 'show', 'id', con_name])
return ssid.strip()
except Exception as e:
logging.warning('Cannot query NetworkManager SSID for {}: {}'.format(
con_name, str(e)))
return ssid
def query_networkctl(self, ifname: str) -> str:
output: str = None
try:
output = subprocess.check_output(['networkctl', 'status', '--', ifname], text=True)
except Exception as e:
logging.warning('Cannot query networkctl for {}: {}'.format(
ifname, str(e)))
return output
def json(self) -> JSON:
json = {
'index': self.idx,
'adminstate': self.adminstate,
'operstate': self.operstate,
}
if self.type:
json['type'] = self.type
if self.ssid:
json['ssid'] = self.ssid
if self.tunnel_mode:
json['tunnel_mode'] = self.tunnel_mode
if self.backend:
json['backend'] = self.backend
if self.netdef_id:
json['id'] = self.netdef_id
if self.macaddress:
json['macaddress'] = self.macaddress
if self.vendor:
json['vendor'] = self.vendor
if self.addresses:
json['addresses'] = self.addresses
if self.dns_addresses:
json['dns_addresses'] = self.dns_addresses
if self.dns_search:
json['dns_search'] = self.dns_search
if self.routes:
json['routes'] = self.routes
if self.activation_mode:
json['activation_mode'] = self.activation_mode
return (self.name, json)
@property
def up(self) -> bool:
return self.adminstate == 'UP' and self.operstate == 'UP'
@property
def down(self) -> bool:
return self.adminstate == 'DOWN' and self.operstate == 'DOWN'
@property
def type(self) -> str:
nd_type = self.nd.get('Type') if self.nd else None
if device_type := DEVICE_TYPES.get(nd_type):
return device_type
logging.warning('Unknown device type: {}'.format(nd_type))
return None
@property
def tunnel_mode(self) -> str:
if self.type == 'tunnel' and self.iproute_type:
return self.iproute_type
return None
@property
def backend(self) -> str:
if (self.nd and
'unmanaged' not in self.nd.get('SetupState', '') and
'run/systemd/network/10-netplan-' in self.nd.get('NetworkFile', '')):
return 'networkd'
elif self.nm and 'run/NetworkManager/system-connections/netplan-' in self.nm.get('filename', ''):
return 'NetworkManager'
return None
@property
def netdef_id(self) -> str:
if self.backend == 'networkd':
return self.nd.get('NetworkFile', '').split(
'run/systemd/network/10-netplan-')[1].split('.network')[0]
elif self.backend == 'NetworkManager':
netdef = self.nm.get('filename', '').split(
'run/NetworkManager/system-connections/netplan-')[1].split('.nmconnection')[0]
if self.nm.get('type', '') == '802-11-wireless':
ssid = self.query_nm_ssid(self.nm.get('name'))
if ssid: # XXX: escaping needed?
netdef = netdef.split('-' + ssid)[0]
return netdef
return None
@property
def vendor(self) -> str:
if self.nd and 'Vendor' in self.nd and self.nd['Vendor']:
return self.nd['Vendor'].strip()
return None
@property
def ssid(self) -> str:
if self.type == 'wifi':
# XXX: available from networkctl's JSON output as of v250:
# https://github.com/systemd/systemd/commit/da7c995
for line in self._networkctl.splitlines():
line = line.strip()
key = 'WiFi access point: '
if line.startswith(key):
ssid = line[len(key):-len(' (xB:SS:ID:xx:xx:xx)')].strip()
return ssid if ssid else None
return None
@property
def activation_mode(self) -> str:
if self.backend == 'networkd':
# XXX: available from networkctl's JSON output as of v250:
# https://github.com/systemd/systemd/commit/3b60ede
for line in self._networkctl.splitlines():
line = line.strip()
key = 'Activation Policy: '
if line.startswith(key):
mode = line[len(key):].strip()
return mode if mode != 'up' else None
# XXX: this is not fully supported on NetworkManager, only 'manual'/'up'
elif self.backend == 'NetworkManager':
return 'manual' if self.nm['autoconnect'] == 'no' else None
return None
class SystemConfigState():
''' Collects the system's network configuration '''
def __init__(self, ifname=None, all=False):
# Make sure sd-networkd is running, as we need the data it provides.
if not utils.systemctl_is_active('systemd-networkd.service'):
if utils.systemctl_is_masked('systemd-networkd.service'):
logging.error('\'netplan status\' depends on networkd, '
'but systemd-networkd.service is masked. '
'Please start it.')
sys.exit(1)
logging.debug('systemd-networkd.service is not active. Starting...')
utils.systemctl('start', ['systemd-networkd.service'], True)
# required data: iproute2 and sd-networkd can be expected to exist,
# due to hard package dependencies
iproute2 = self.query_iproute2()
networkd = self.query_networkd()
if not iproute2 or not networkd:
logging.error('Could not query iproute2 or systemd-networkd')
sys.exit(1)
# optional data
nmcli = self.query_nm()
route4, route6 = self.query_routes()
dns_addresses, dns_search = self.query_resolved()
self.interface_list = [Interface(itf, networkd, nmcli, (dns_addresses, dns_search),
(route4, route6)) for itf in iproute2]
# show only active interfaces by default
filtered = [itf for itf in self.interface_list if itf.operstate != 'DOWN']
# down interfaces do not contribute anything to the online state
online_state = self.query_online_state(filtered)
# show only a single interface, if requested
# XXX: bash completion (for interfaces names)
if ifname:
filtered = [next((itf for itf in self.interface_list if itf.name == ifname), None)]
filtered = [elem for elem in filtered if elem is not None]
if ifname and filtered == []:
logging.error('Could not find interface {}'.format(ifname))
sys.exit(1)
# Global state
self.state = {
'netplan-global-state': {
'online': online_state,
'nameservers': self.resolvconf_json()
}
}
# Per interface
itf_iter = self.interface_list if all else filtered
for itf in itf_iter:
ifname, obj = itf.json()
self.state[ifname] = obj
@classmethod
def resolvconf_json(cls) -> dict:
res = {
'addresses': [],
'search': [],
'mode': None,
}
try:
with open('/etc/resolv.conf') as f:
# check first line for systemd-resolved stub or compat modes
firstline = f.readline()
if '# This is /run/systemd/resolve/stub-resolv.conf' in firstline:
res['mode'] = 'stub'
elif '# This is /run/systemd/resolve/resolv.conf' in firstline:
res['mode'] = 'compat'
for line in [firstline] + f.readlines():
if line.startswith('nameserver'):
res['addresses'] += line.split()[1:] # append
if line.startswith('search'):
res['search'] = line.split()[1:] # override
except Exception as e:
logging.warning('Cannot parse /etc/resolv.conf: {}'.format(str(e)))
return res
@classmethod
def query_online_state(cls, interfaces: list) -> bool:
# TODO: fully implement network-online.target specification (FO020):
# https://discourse.ubuntu.com/t/spec-definition-of-an-online-system/27838
for itf in interfaces:
if itf.up and itf.addresses and itf.routes and itf.dns_addresses:
non_local_ips = []
for addr in itf.addresses:
ip, extra = list(addr.items())[0]
if 'flags' not in extra or 'link' not in extra['flags']:
non_local_ips.append(ip)
default_routes = [x for x in itf.routes if x.get('to', None) == 'default']
if non_local_ips and default_routes and itf.dns_addresses:
return True
return False
@classmethod
def process_generic(cls, cmd_output: str) -> JSON:
return json.loads(cmd_output)
@classmethod
def query_iproute2(cls) -> JSON:
data: JSON = None
try:
output: str = subprocess.check_output(['ip', '-d', '-j', 'addr'],
text=True)
data = cls.process_generic(output)
except Exception as e:
logging.critical('Cannot query iproute2 interface data: {}'.format(str(e)))
return data
@classmethod
def process_networkd(cls, cmd_output) -> JSON:
return json.loads(cmd_output)['Interfaces']
@classmethod
def query_networkd(cls) -> JSON:
data: JSON = None
try:
output: str = subprocess.check_output(['networkctl', '--json=short'],
text=True)
data = cls.process_networkd(output)
except Exception as e:
logging.critical('Cannot query networkd interface data: {}'.format(str(e)))
return data
@classmethod
def process_nm(cls, cmd_output) -> JSON:
data: JSON = []
for line in cmd_output.splitlines():
split = line.split(':')
dev = split[0] if split[0] else None
if dev: # ignore inactive connection profiles
data.append({
'device': dev,
'name': split[1],
'uuid': split[2],
'filename': split[3],
'type': split[4],
'autoconnect': split[5],
})
return data
@classmethod
def query_nm(cls) -> JSON:
data: JSON = None
try:
output: str = utils.nmcli_out(['-t', '-f',
'DEVICE,NAME,UUID,FILENAME,TYPE,AUTOCONNECT',
'con', 'show'])
data = cls.process_nm(output)
except Exception as e:
logging.debug('Cannot query NetworkManager interface data: {}'.format(str(e)))
return data
@classmethod
def query_routes(cls) -> tuple:
data4 = None
data6 = None
try:
output4: str = subprocess.check_output(['ip', '-d', '-j', '-4', 'route', 'show', 'table', 'all'],
text=True)
data4: JSON = cls.process_generic(output4)
output6: str = subprocess.check_output(['ip', '-d', '-j', '-6', 'route', 'show', 'table', 'all'],
text=True)
data6: JSON = cls.process_generic(output6)
except Exception as e:
logging.debug('Cannot query iproute2 route data: {}'.format(str(e)))
# Add the address family to the data
# IPv4: 2, IPv6: 10
if data4:
for route in data4:
route.update({'family': socket.AF_INET.value})
if data6:
for route in data6:
route.update({'family': socket.AF_INET6.value})
return (data4, data6)
@classmethod
def query_resolved(cls) -> tuple:
addresses = None
search = None
try:
ipc = dbus.SystemBus()
resolve1 = ipc.get_object('org.freedesktop.resolve1', '/org/freedesktop/resolve1')
resolve1_if = dbus.Interface(resolve1, 'org.freedesktop.DBus.Properties')
res = resolve1_if.GetAll('org.freedesktop.resolve1.Manager')
addresses = res['DNS']
search = res['Domains']
except Exception as e:
logging.debug('Cannot query resolved DNS data: {}'.format(str(e)))
return (addresses, search)
@property
def number_of_interfaces(self) -> int:
return len(self.interface_list)
def get_data(self) -> dict:
return self.state
class NetplanConfigState():
''' Collects the Netplan's network configuration '''
def __init__(self, subtree='all', rootdir='/'):
parser = netplan.Parser()
parser.load_yaml_hierarchy(rootdir)
np_state = netplan.State()
np_state.import_parser_results(parser)
self.state = StringIO()
if subtree == 'all':
np_state._dump_yaml(output_file=self.state)
else:
if not subtree.startswith('network'):
subtree = '.'.join(('network', subtree))
# Split at '.' but not at '\.' via negative lookbehind expression
subtree = re.split(r'(? str:
return self.state.getvalue()
def get_data(self) -> dict:
return yaml.safe_load(self.state.getvalue())
netplan-0.107.1/netplan_cli/cli/utils.py 0000664 0000000 0000000 00000024130 14536110317 0020073 0 ustar 00root root 0000000 0000000 # Copyright (C) 2018-2020 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
# Author: Łukasz 'sil2100' Zemczak
# Author: Lukas 'slyon' Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
import sys
import os
import logging
import argparse
import subprocess
import netifaces
import fnmatch
import re
from ..configmanager import ConfigurationError
from netplan import NetDefinition, NetplanException
NM_SERVICE_NAME = 'NetworkManager.service'
NM_SNAP_SERVICE_NAME = 'snap.network-manager.networkmanager.service'
config_errors = (ConfigurationError, NetplanException, RuntimeError)
def get_generator_path():
return os.environ.get('NETPLAN_GENERATE_PATH', '/usr/libexec/netplan/generate')
def is_nm_snap_enabled():
return subprocess.call(['systemctl', '--quiet', 'is-enabled', NM_SNAP_SERVICE_NAME], stderr=subprocess.DEVNULL) == 0
def nmcli(args): # pragma: nocover (covered in autopkgtest)
# 'nmcli' could be /usr/bin/nmcli or /snap/bin/nmcli -> /snap/bin/network-manager.nmcli
# PATH is defined in cli/core.py
subprocess.check_call(['nmcli'] + args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def nmcli_out(args: list) -> str: # pragma: nocover (covered in autopkgtest)
# 'nmcli' could be /usr/bin/nmcli or /snap/bin/nmcli -> /snap/bin/network-manager.nmcli
# PATH is defined in cli/core.py
return subprocess.check_output(['nmcli'] + args, text=True)
def nm_running(): # pragma: nocover (covered in autopkgtest)
'''Check if NetworkManager is running'''
try:
nmcli(['general'])
return True
except (OSError, subprocess.SubprocessError):
return False
def nm_interfaces(paths, devices):
pat = re.compile('^interface-name=(.*)$')
interfaces = set()
for path in paths:
with open(path, 'r') as f:
for line in f:
m = pat.match(line)
if m:
# Expand/match globbing of interface names, to real devices
interfaces.update(set(fnmatch.filter(devices, m.group(1))))
break # skip to next file
return interfaces
def nm_get_connection_for_interface(interface: str) -> str:
output = nmcli_out(['-m', 'tabular', '-f', 'GENERAL.CONNECTION', 'device', 'show', interface])
lines = output.strip().split('\n')
connection = lines[1]
return connection if connection != '--' else ''
def nm_bring_interface_up(connection: str) -> None: # pragma: nocover (must be covered by NM autopkgtests)
try:
nmcli(['connection', 'up', connection])
except subprocess.CalledProcessError:
pass
def systemctl_network_manager(action, sync=False):
# If the network-manager snap is installed use its service
# name rather than the one of the deb packaged NetworkManager
if is_nm_snap_enabled():
return systemctl(action, [NM_SNAP_SERVICE_NAME], sync)
return systemctl(action, [NM_SERVICE_NAME], sync) # pragma: nocover (covered in autopkgtest)
def systemctl(action: str, services: list, sync: bool = False):
if len(services) >= 1:
command = ['systemctl', action]
if not sync:
command.append('--no-block')
command.extend(services)
subprocess.check_call(command)
def networkd_interfaces():
interfaces = set()
out = subprocess.check_output(['networkctl', '--no-pager', '--no-legend'], text=True)
for line in out.splitlines():
s = line.strip().split(' ')
if s[0].isnumeric() and s[-1] not in ['unmanaged', 'linger']:
interfaces.add(s[0])
return interfaces
def networkctl_reload():
subprocess.check_call(['networkctl', 'reload'])
def networkctl_reconfigure(interfaces):
if len(interfaces) >= 1:
subprocess.check_call(['networkctl', 'reconfigure'] + list(interfaces))
def systemctl_is_active(unit_pattern):
'''Return True if at least one matching unit is running'''
if subprocess.call(['systemctl', '--quiet', 'is-active', unit_pattern]) == 0:
return True
return False
def systemctl_is_masked(unit_pattern):
'''Return True if output is "masked" or "masked-runtime"'''
res = subprocess.run(['systemctl', 'is-enabled', unit_pattern],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True)
if res.returncode > 0 and 'masked' in res.stdout:
return True
return False
def systemctl_daemon_reload():
'''Reload systemd unit files from disk and re-calculate its dependencies'''
subprocess.check_call(['systemctl', 'daemon-reload'])
def ip_addr_flush(iface):
'''Flush all IP addresses of a given interface via iproute2'''
subprocess.check_call(['ip', 'addr', 'flush', iface], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def get_interface_driver_name(interface, only_down=False): # pragma: nocover (covered in autopkgtest)
devdir = os.path.join('/sys/class/net', interface)
if only_down:
try:
with open(os.path.join(devdir, 'operstate')) as f:
state = f.read().strip()
if state != 'down':
logging.debug('device %s operstate is %s, not changing', interface, state)
return None
except IOError as e:
logging.error('Cannot determine operstate of %s: %s', interface, str(e))
return None
try:
driver = os.path.realpath(os.path.join(devdir, 'device', 'driver'))
driver_name = os.path.basename(driver)
except IOError as e:
logging.debug('Cannot replug %s: cannot read link %s/device: %s', interface, devdir, str(e))
return None
return driver_name
def get_interface_macaddress(interface):
# return an empty list (and string) if no LL data can be found
link = netifaces.ifaddresses(interface).get(netifaces.AF_LINK, [{}])[0]
return link.get('addr', '')
def find_matching_iface(interfaces: list, netdef):
assert isinstance(netdef, NetDefinition)
assert netdef._has_match
matches = list(filter(lambda itf: netdef._match_interface(
iface_name=itf,
iface_driver=get_interface_driver_name(itf),
iface_mac=get_interface_macaddress(itf)), interfaces))
# Return current name of unique matched interface, if available
if len(matches) != 1:
logging.info(matches)
return None
return matches[0]
class NetplanCommand(argparse.Namespace):
def __init__(self, command_id, description, leaf=True, testing=False):
self.command_id = command_id
self.description = description
self.leaf_command = leaf
self.testing = testing
self._args = None
self.debug = False
self.breakpoint = False
self.commandclass = None
self.subcommands = {}
self.subcommand = None
self.func = None
self.parser = argparse.ArgumentParser(prog="%s %s" % (sys.argv[0], command_id),
description=description,
add_help=True)
self.parser.add_argument('--debug', action='store_true',
help='Enable debug messages')
self.parser.add_argument('--breakpoint', action='store_true',
help=argparse.SUPPRESS)
if not leaf:
self.subparsers = self.parser.add_subparsers(title='Available commands',
metavar='', dest='subcommand')
p_help = self.subparsers.add_parser('help',
description='Show this help message',
help='Show this help message')
p_help.set_defaults(func=self.print_usage)
def update(self, args):
self._args = args
def parse_args(self):
ns, self._args = self.parser.parse_known_args(args=self._args, namespace=self)
if not self.subcommand and not self.leaf_command:
print('You need to specify a command', file=sys.stderr)
self.print_usage()
def run_command(self):
if self.commandclass:
self.commandclass.update(self._args)
# TODO: (cyphermox) this is actually testable in tests/cli.py; add it.
if self.leaf_command and 'help' in self._args: # pragma: nocover (covered in autopkgtest)
self.print_usage()
if self.breakpoint: # pragma: nocover (cannot be automatically tested)
breakpoint()
self.func()
def print_usage(self):
self.parser.print_help(file=sys.stderr)
sys.exit(os.EX_USAGE)
def _add_subparser_from_class(self, name, commandclass):
instance = commandclass()
self.subcommands[name] = {}
self.subcommands[name]['class'] = name
self.subcommands[name]['instance'] = instance
if instance.testing:
if not os.environ.get('ENABLE_TEST_COMMANDS', None):
return
p = self.subparsers.add_parser(instance.command_id,
description=instance.description,
help=instance.description,
add_help=False)
p.set_defaults(func=instance.run, commandclass=instance)
self.subcommands[name]['parser'] = p
def _import_subcommands(self, submodules):
import inspect
for name, obj in inspect.getmembers(submodules):
if inspect.isclass(obj) and issubclass(obj, NetplanCommand):
self._add_subparser_from_class(name, obj)
netplan-0.107.1/netplan_cli/configmanager.py 0000664 0000000 0000000 00000014672 14536110317 0020776 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan configuration manager'''
import logging
import netplan
import os
import shutil
import sys
import tempfile
from typing import Optional
class ConfigManager(object):
def __init__(self, prefix="/", extra_files={}):
self.prefix = prefix
self.tempdir = tempfile.mkdtemp(prefix='netplan_')
self.temp_etc = os.path.join(self.tempdir, "etc")
self.temp_run = os.path.join(self.tempdir, "run")
self.extra_files = extra_files
self.new_interfaces = set()
self.np_state: Optional[netplan.State] = None
def __getattr__(self, attr):
assert self.np_state is not None, "Must call parse() before accessing the config."
return getattr(self.np_state, attr)
@property
def physical_interfaces(self):
assert self.np_state is not None, "Must call parse() before accessing the config."
interfaces = {}
interfaces.update(self.np_state.ethernets)
interfaces.update(self.np_state.modems)
interfaces.update(self.np_state.wifis)
return interfaces
@property
def virtual_interfaces(self):
assert self.np_state is not None, "Must call parse() before accessing the config."
interfaces = {}
# what about ovs_ports?
interfaces.update(self.np_state.bridges)
interfaces.update(self.np_state.bonds)
interfaces.update(self.np_state.dummy_devices)
interfaces.update(self.np_state.tunnels)
interfaces.update(self.np_state.virtual_ethernets)
interfaces.update(self.np_state.vlans)
interfaces.update(self.np_state.vrfs)
return interfaces
def parse(self, extra_config=None):
"""
Parse all our config files to return an object that describes the system's
entire configuration, so that it can later be interrogated.
Returns a libnetplan State wrapper
"""
# /run/netplan shadows /etc/netplan/, which shadows /lib/netplan
parser = netplan.Parser()
try:
parser.load_yaml_hierarchy(rootdir=self.prefix)
if extra_config:
for f in extra_config:
parser.load_yaml(f)
self.np_state = netplan.State()
self.np_state.import_parser_results(parser)
except netplan.NetplanException as e:
raise ConfigurationError(str(e))
# Convoluted way to dump the parsed config to the logs...
with tempfile.TemporaryFile() as tmp:
self.np_state._dump_yaml(output_file=tmp)
logging.debug("Merged config:\n{}".format(tmp.read()))
return self.np_state
def add(self, config_dict):
for config_file in config_dict:
self._copy_file(config_file, config_dict[config_file])
self.extra_files.update(config_dict)
# Invalidate the current parsed state
self.np_state = None
def backup(self, backup_config_dir=True):
if backup_config_dir:
self._copy_tree(os.path.join(self.prefix, "etc/netplan"),
os.path.join(self.temp_etc, "netplan"))
self._copy_tree(os.path.join(self.prefix, "run/NetworkManager/system-connections"),
os.path.join(self.temp_run, "NetworkManager", "system-connections"),
missing_ok=True)
self._copy_tree(os.path.join(self.prefix, "run/systemd/network"),
os.path.join(self.temp_run, "systemd", "network"),
missing_ok=True)
def revert(self):
try:
for extra_file in dict(self.extra_files):
os.unlink(self.extra_files[extra_file])
del self.extra_files[extra_file]
temp_nm_path = "{}/NetworkManager/system-connections".format(self.temp_run)
temp_networkd_path = "{}/systemd/network".format(self.temp_run)
if os.path.exists(temp_nm_path):
shutil.rmtree(os.path.join(self.prefix, "run/NetworkManager/system-connections"))
self._copy_tree(temp_nm_path,
os.path.join(self.prefix, "run/NetworkManager/system-connections"))
if os.path.exists(temp_networkd_path):
shutil.rmtree(os.path.join(self.prefix, "run/systemd/network"))
self._copy_tree(temp_networkd_path,
os.path.join(self.prefix, "run/systemd/network"))
except Exception as e: # pragma: nocover (only relevant to filesystem failures)
# If we reach here, we're in big trouble. We may have wiped out
# file NM or networkd are using, and we most likely removed the
# "new" config -- or at least our copy of it.
# Given that we're in some halfway done revert; warn the user
# aggressively and drop everything; leaving any remaining backups
# around for the user to handle themselves.
logging.error("Something really bad happened while reverting config: {}".format(e))
logging.error("You should verify the netplan YAML in /etc/netplan and probably run 'netplan apply' again.")
sys.exit(-1)
def cleanup(self):
shutil.rmtree(self.tempdir)
def __del__(self):
try:
self.cleanup()
except FileNotFoundError:
# If cleanup() was called before, there is nothing to delete
pass
def _copy_file(self, src, dst):
shutil.copy(src, dst)
def _copy_tree(self, src, dst, missing_ok=False):
try:
shutil.copytree(src, dst)
except FileNotFoundError:
if missing_ok:
pass
else:
raise
class ConfigurationError(Exception):
"""
Configuration could not be parsed or has otherwise failed to apply
"""
pass
netplan-0.107.1/netplan_cli/meson.build 0000664 0000000 0000000 00000002501 14536110317 0017752 0 ustar 00root root 0000000 0000000 install_data('../src/netplan.script')
install_symlink(
'netplan',
pointing_to: '../share/netplan/netplan.script',
install_dir: get_option('sbindir'))
netplan_module = join_paths(get_option('datadir'), meson.project_name(), 'netplan_cli')
features_py = custom_target(
build_always_stale: true,
output: '_features.py',
input: join_paths(meson.project_source_root(), 'features_py_generator.sh'),
command: ['sh', '-c', '@INPUT@'],
install: true,
install_dir: netplan_module,
capture: true,
)
netplan_sources = files(
'__init__.py',
'configmanager.py',
'terminal.py')
cli_sources = files(
'cli/__init__.py',
'cli/core.py',
'cli/ovs.py',
'cli/state.py',
'cli/sriov.py',
'cli/utils.py')
commands_sources = files(
'cli/commands/__init__.py',
'cli/commands/apply.py',
'cli/commands/generate.py',
'cli/commands/get.py',
'cli/commands/info.py',
'cli/commands/ip.py',
'cli/commands/migrate.py',
'cli/commands/set.py',
'cli/commands/sriov_rebind.py',
'cli/commands/status.py',
'cli/commands/try_command.py')
install_data(netplan_sources, install_dir: netplan_module)
install_data(cli_sources, install_dir: join_paths(netplan_module, 'cli'))
install_data(commands_sources, install_dir: join_paths(netplan_module, 'cli', 'commands'))
netplan-0.107.1/netplan_cli/terminal.py 0000664 0000000 0000000 00000012273 14536110317 0020004 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
"""
Terminal / input handling
"""
import fcntl
import os
import termios
import select
import sys
class Terminal(object):
"""
Do minimal terminal mangling to prompt users for input
"""
def __init__(self, fd):
self.fd = fd
self.orig_flags = None
self.orig_term = None
self.save()
def enable_echo(self):
if sys.stdin.isatty():
attrs = termios.tcgetattr(self.fd)
attrs[3] = attrs[3] | termios.ICANON
attrs[3] = attrs[3] | termios.ECHO
termios.tcsetattr(self.fd, termios.TCSANOW, attrs)
def disable_echo(self):
if sys.stdin.isatty():
attrs = termios.tcgetattr(self.fd)
attrs[3] = attrs[3] & ~termios.ICANON
attrs[3] = attrs[3] & ~termios.ECHO
termios.tcsetattr(self.fd, termios.TCSANOW, attrs)
def enable_nonblocking_io(self):
flags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
fcntl.fcntl(self.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
def disable_nonblocking_io(self):
flags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
fcntl.fcntl(self.fd, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
def get_confirmation_input(self, timeout=120, message=None): # pragma: nocover (requires user input)
"""
Get a "confirmation" input from the user, for at most (timeout)
seconds. Optionally, customize the message to be displayed.
timeout -- timeout to wait for input (default 120)
message -- optional customized message ("Press ENTER to (message)")
raises:
InputAccepted -- the user confirmed the changes
InputRejected -- the user rejected the changes
"""
print("Do you want to keep these settings?\n\n")
settings = dict()
self.save(settings)
self.disable_echo()
self.enable_nonblocking_io()
if not message:
message = "accept the new configuration"
print("Press ENTER before the timeout to {}\n\n".format(message))
timeout_now = timeout
while (timeout_now > 0):
print("Changes will revert in {:>{}} seconds".format(timeout_now, len(str(timeout))), end='\r')
# wait at most 1 second for usable input from stdin
select.select([sys.stdin], [], [], 1)
try:
# retrieve any input from the terminal. select() either has
# timed out with no input, or found something we can retrieve.
c = sys.stdin.read()
if (c == '\n'):
self.reset(settings)
# Yay, user has accepted the changes!
raise InputAccepted()
except TypeError:
# read() above is non-blocking, if there is nothing to read it
# will return TypeError, which we should ignore -- on to the
# next iteration until timeout.
pass
timeout_now -= 1
# We reached the timeout for our loop, now revert our change for
# non-blocking I/O and signal the caller the changes were essentially
# rejected.
self.reset(settings)
raise InputRejected()
def save(self, dest=None):
"""
Save the terminal's current attributes and flags
Optional argument:
- dest: if set, save settings to this dict
"""
orig_flags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
orig_term = None
if sys.stdin.isatty():
orig_term = termios.tcgetattr(self.fd)
if dest is not None:
dest.update({'flags': orig_flags,
'term': orig_term})
else:
self.orig_flags = orig_flags
self.orig_term = orig_term
def reset(self, orig=None):
"""
Reset the terminal to its original attributes and flags
Optional argument:
- orig: if set, reset to settings from this dict
"""
orig_term = None
orig_flags = None
if orig is not None:
orig_term = orig.get('term')
orig_flags = orig.get('flags')
else:
orig_term = self.orig_term
orig_flags = self.orig_flags
if sys.stdin.isatty():
termios.tcsetattr(self.fd, termios.TCSAFLUSH, orig_term)
fcntl.fcntl(self.fd, fcntl.F_SETFL, orig_flags)
class InputAccepted(Exception):
""" Denotes has accepted input"""
pass
class InputRejected(Exception):
""" Denotes that the user has rejected input"""
pass
netplan-0.107.1/python-cffi/ 0000775 0000000 0000000 00000000000 14536110317 0015550 5 ustar 00root root 0000000 0000000 netplan-0.107.1/python-cffi/meson.build 0000664 0000000 0000000 00000000022 14536110317 0017704 0 ustar 00root root 0000000 0000000 subdir('netplan')
netplan-0.107.1/python-cffi/netplan/ 0000775 0000000 0000000 00000000000 14536110317 0017211 5 ustar 00root root 0000000 0000000 netplan-0.107.1/python-cffi/netplan/__init__.py 0000664 0000000 0000000 00000005512 14536110317 0021325 0 ustar 00root root 0000000 0000000 # Copyright (C) 2023 Canonical, Ltd.
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
from io import StringIO
import json
import os
from typing import Union, List, IO
from ._netplan_cffi import lib
from .netdef import NetDefinition, NetDefinitionIterator
from .parser import Parser
from .state import State
from ._utils import _checked_lib_call
from ._utils import (NetplanException, NetplanBackendException,
NetplanEmitterException, NetplanFileException,
NetplanFormatException, NetplanParserException,
NetplanValidationException)
def _dump_yaml_subtree(prefix: List[str], input_file: IO, output_file: IO):
if isinstance(input_file, StringIO):
input_fd = os.memfd_create(name='netplan_temp_input_file')
data = input_file.getvalue()
os.write(input_fd, data.encode('utf-8'))
os.lseek(input_fd, 0, os.SEEK_SET)
else:
input_fd = input_file.fileno()
if isinstance(output_file, StringIO):
output_fd = os.memfd_create(name='netplan_temp_output_file')
else:
output_fd = output_file.fileno()
_checked_lib_call(lib.netplan_util_dump_yaml_subtree, '\t'.join(prefix).encode('utf-8'), input_fd, output_fd)
if isinstance(input_file, StringIO):
os.close(input_fd)
if isinstance(output_file, StringIO):
size = os.lseek(output_fd, 0, os.SEEK_CUR)
os.lseek(output_fd, 0, os.SEEK_SET)
data = os.read(output_fd, size)
output_file.write(data.decode('utf-8'))
os.close(output_fd)
def _create_yaml_patch(patch_object_path: List[str], patch_payload: Union[str, dict], patch_output: IO):
if isinstance(patch_payload, dict):
patch_payload = json.dumps(patch_payload)
_checked_lib_call(lib.netplan_util_create_yaml_patch,
'\t'.join(patch_object_path).encode('utf-8'),
patch_payload.encode('utf-8'),
patch_output.fileno())
# Re-export submodules
__all__ = [Parser, State, NetDefinition, NetDefinitionIterator,
_dump_yaml_subtree, _create_yaml_patch,
NetplanException, NetplanBackendException, NetplanEmitterException,
NetplanFileException, NetplanFormatException, NetplanParserException,
NetplanValidationException]
netplan-0.107.1/python-cffi/netplan/_build_cffi.py 0000664 0000000 0000000 00000020560 14536110317 0022013 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3
# Copyright (C) 2023 Canonical, Ltd.
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
import os
import sys
from cffi import FFI
ffibuilder = FFI()
# cdef() expects a single string declaring the C types, functions and
# globals needed to use the shared object. It must be in valid C syntax.
ffibuilder.cdef("""
#define UINT_MAX ...
typedef int gboolean;
typedef unsigned int guint;
typedef int gint;
typedef struct GError NetplanError;
typedef struct netplan_parser NetplanParser;
typedef struct netplan_state NetplanState;
typedef struct netplan_net_definition NetplanNetDefinition;
typedef enum { ... } NetplanBackend;
typedef enum { ... } NetplanDefType;
// TODO: Introduce getters for .address/.lifetime/.label to avoid exposing the raw struct
typedef struct {
char* address;
char* lifetime;
char* label;
} NetplanAddressOptions;
struct address_iter { ...; };
struct nameserver_iter { ...; };
struct route_iter { ...; };
// TODO: Introduce getters for all these fields to avoid exposing the raw struct
typedef struct {
gint family;
char* type;
char* scope;
guint table;
char* from;
char* to;
char* via;
gboolean onlink;
guint metric;
guint mtubytes;
guint congestion_window;
guint advertised_receive_window;
} NetplanIPRoute;
// Error handling
uint64_t netplan_error_code(NetplanError* error);
ssize_t netplan_error_message(NetplanError* error, char* buf, size_t buf_size);
// Parser
NetplanParser* netplan_parser_new();
void netplan_parser_clear(NetplanParser **npp);
gboolean netplan_parser_load_yaml(NetplanParser* npp, const char* filename, NetplanError** error);
gboolean netplan_parser_load_yaml_from_fd(NetplanParser* npp, int input_fd, NetplanError** error);
gboolean netplan_parser_load_yaml_hierarchy(NetplanParser* npp, const char* rootdir, NetplanError** error);
gboolean netplan_parser_load_keyfile(NetplanParser* npp, const char* filename, NetplanError** error);
gboolean netplan_parser_load_nullable_fields(NetplanParser* npp, int input_fd, NetplanError** error);
gboolean netplan_parser_load_nullable_overrides(
NetplanParser* npp, int input_fd, const char* constraint, NetplanError** error);
// State
NetplanState* netplan_state_new();
void netplan_state_clear(NetplanState** np_state);
NetplanBackend netplan_state_get_backend(const NetplanState* np_state);
gboolean netplan_state_import_parser_results(NetplanState* np_state, NetplanParser* npp, NetplanError** error);
gboolean netplan_state_update_yaml_hierarchy(
const NetplanState* np_state, const char* default_filename, const char* rootdir, NetplanError** error);
gboolean netplan_state_write_yaml_file(
const NetplanState* np_state, const char* filename, const char* rootdir, NetplanError** error);
gboolean netplan_state_dump_yaml(const NetplanState* np_state, int output_fd, NetplanError** error);
NetplanNetDefinition* netplan_state_get_netdef(const NetplanState* np_state, const char* id);
guint netplan_state_get_netdefs_size(const NetplanState* np_state);
// NetDefinition
ssize_t netplan_netdef_get_id(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buffer_size);
NetplanDefType netplan_netdef_get_type(const NetplanNetDefinition* netdef);
NetplanBackend netplan_netdef_get_backend(const NetplanNetDefinition* netdef);
ssize_t netplan_netdef_get_filepath(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buffer_size);
NetplanNetDefinition* netplan_netdef_get_bridge_link(const NetplanNetDefinition* netdef);
NetplanNetDefinition* netplan_netdef_get_bond_link(const NetplanNetDefinition* netdef);
NetplanNetDefinition* netplan_netdef_get_peer_link(const NetplanNetDefinition* netdef);
NetplanNetDefinition* netplan_netdef_get_vlan_link(const NetplanNetDefinition* netdef);
NetplanNetDefinition* netplan_netdef_get_sriov_link(const NetplanNetDefinition* netdef);
ssize_t netplan_netdef_get_set_name(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buffer_size);
gboolean netplan_netdef_has_match(const NetplanNetDefinition* netdef);
gboolean netplan_netdef_get_delay_virtual_functions_rebind(const NetplanNetDefinition* netdef);
gboolean netplan_netdef_match_interface(
const NetplanNetDefinition* netdef, const char* name, const char* mac, const char* driver_name);
gboolean netplan_netdef_get_dhcp4(const NetplanNetDefinition* netdef);
gboolean netplan_netdef_get_dhcp6(const NetplanNetDefinition* netdef);
ssize_t netplan_netdef_get_macaddress(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buffer_size);
// NetDefinition (internal)
ssize_t _netplan_netdef_get_embedded_switch_mode(const NetplanNetDefinition* netdef, char* out_buffer, size_t out_buf_size);
gboolean _netplan_netdef_get_sriov_vlan_filter(const NetplanNetDefinition* netdef);
guint _netplan_netdef_get_vlan_id(const NetplanNetDefinition* netdef);
gboolean _netplan_netdef_get_critical(const NetplanNetDefinition* netdef);
gboolean _netplan_netdef_is_trivial_compound_itf(const NetplanNetDefinition* netdef);
int _netplan_state_get_vf_count_for_def(
const NetplanState* np_state, const NetplanNetDefinition* netdef, NetplanError** error);
// Iterators (internal)
struct netdef_pertype_iter* _netplan_state_new_netdef_pertype_iter(NetplanState* np_state, const char* def_type);
NetplanNetDefinition* _netplan_netdef_pertype_iter_next(struct netdef_pertype_iter* it);
void _netplan_netdef_pertype_iter_free(struct netdef_pertype_iter* it);
struct address_iter* _netplan_netdef_new_address_iter(NetplanNetDefinition* netdef);
NetplanAddressOptions* _netplan_address_iter_next(struct address_iter* it);
void _netplan_address_iter_free(struct address_iter* it);
struct nameserver_iter* _netplan_netdef_new_nameserver_iter(NetplanNetDefinition* netdef);
char* _netplan_nameserver_iter_next(struct nameserver_iter* it);
void _netplan_nameserver_iter_free(struct nameserver_iter* it);
struct nameserver_iter* _netplan_netdef_new_search_domain_iter(NetplanNetDefinition* netdef);
char* _netplan_search_domain_iter_next(struct nameserver_iter* it);
void _netplan_search_domain_iter_free(struct nameserver_iter* it);
struct route_iter* _netplan_netdef_new_route_iter(NetplanNetDefinition* netdef);
NetplanIPRoute* _netplan_route_iter_next(struct route_iter* it);
void _netplan_route_iter_free(struct route_iter* it);
// Utils
gboolean netplan_util_dump_yaml_subtree(const char* prefix, int input_fd, int output_fd, NetplanError** error);
gboolean netplan_util_create_yaml_patch(const char* conf_obj_path, const char* obj_payload, int out_fd, NetplanError** error);
// Names (internal)
const char* netplan_backend_name(NetplanBackend val);
const char* netplan_def_type_name(NetplanDefType val);
""")
cffi_inc = os.getenv('CFFI_INC', sys.argv[1])
cffi_lib = os.getenv('CFFI_LIB', sys.argv[2])
# set_source() gives the name of the python extension module to
# produce, and some C source code as a string. This C code needs
# to make the declarated functions, types and globals available,
# so it is often just the "#include".
ffibuilder.set_source_pkgconfig(
"_netplan_cffi", ['glib-2.0'],
"""
#include
// C API of libnetplan.so
#include "netplan.h"
#include "parse.h"
#include "parse-nm.h"
#include "util.h"
// internal headers (private API)
#include "util-internal.h"
#include "names.h"
""",
include_dirs=[cffi_inc],
library_dirs=[cffi_lib],
libraries=['glib-2.0']) # library name, for the linker
if __name__ == "__main__":
ffibuilder.distutils_extension('.')
netplan-0.107.1/python-cffi/netplan/_utils.py 0000664 0000000 0000000 00000016633 14536110317 0021073 0 ustar 00root root 0000000 0000000 # Copyright (C) 2023 Canonical, Ltd.
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
from collections import defaultdict
from enum import IntEnum
import re
from ._netplan_cffi import ffi, lib
# Errors and error domains
# NOTE: if new errors or domains are added,
# include/types.h must be updated with the new entries
class NETPLAN_ERROR_DOMAINS(IntEnum):
NETPLAN_PARSER_ERROR = 1
NETPLAN_VALIDATION_ERROR = 2
NETPLAN_FILE_ERROR = 3
NETPLAN_BACKEND_ERROR = 4
NETPLAN_EMITTER_ERROR = 5
NETPLAN_FORMAT_ERROR = 6
class NETPLAN_PARSER_ERRORS(IntEnum):
NETPLAN_ERROR_INVALID_YAML = 0
NETPLAN_ERROR_INVALID_CONFIG = 1
class NETPLAN_VALIDATION_ERRORS(IntEnum):
NETPLAN_ERROR_CONFIG_GENERIC = 0
NETPLAN_ERROR_CONFIG_VALIDATION = 1
class NETPLAN_BACKEND_ERRORS(IntEnum):
NETPLAN_ERROR_UNSUPPORTED = 0
NETPLAN_ERROR_VALIDATION = 1
class NETPLAN_EMITTER_ERRORS(IntEnum):
NETPLAN_ERROR_YAML_EMITTER = 0
class NETPLAN_FORMAT_ERRORS(IntEnum):
NETPLAN_ERROR_FORMAT_INVALID_YAML = 0
class NetplanException(Exception):
def __init__(self, message=None, domain=None, error=None):
self.domain = domain
self.error = error
self.message = message
def __str__(self):
return self.message
class NetplanFileException(NetplanException):
@property
def errno(self):
return self.error
class NetplanValidationException(NetplanException):
'''
Netplan Validation errors are expected to contain the YAML file name
from where the error was found.
A validation error might happen after the parsing stage. libnetplan walks
through its internal representation of the network configuration and checks
if all the requirements are met. For example, if it finds that the key
"set-name" is used by an interface, it will check if "match" is present.
As "set-name" requires "match" to work, it will emit a validation error
if it's not found.
'''
SCHEMA_VALIDATION_ERROR_MSG_REGEX = (
r'(?P.*\.yaml): (?P.*)'
)
def __init__(self, message=None, domain=None, error=None):
super().__init__(message, domain, error)
schema_error = re.match(self.SCHEMA_VALIDATION_ERROR_MSG_REGEX, message)
if not schema_error:
# This shouldn't happen
raise ValueError(f'The validation error message does not have the expected format: {message}')
self.filename = schema_error["file_path"]
self.message = schema_error["message"]
class NetplanParserException(NetplanException):
'''
Netplan Parser errors are expected to contain the YAML file name
and line and column numbers from where the error was found.
A parser error might happen during the parsing stage. Parsing errors
might be due to invalid YAML files or invalid Netplan grammar. libnetplan
will check for this kind of issues while it's walking through the YAML
files, so it has access to the location where the error was found.
'''
SCHEMA_PARSER_ERROR_MSG_REGEX = (
r'(?P.*):(?P\d+):(?P\d+): (?P(\s|.)*)'
)
def __init__(self, message=None, domain=None, error=None):
super().__init__(message, domain, error)
# Parser errors from libnetplan have the form:
#
# filename.yaml:4:14: Error in network definition: invalid boolean value 'falsea'
#
schema_error = re.match(self.SCHEMA_PARSER_ERROR_MSG_REGEX, message)
if not schema_error:
# This shouldn't happen
raise ValueError(f'The parser error message does not have the expected format: {message}')
self.filename = schema_error["file_path"]
self.line = schema_error["error_line"]
self.column = schema_error["error_col"]
self.message = schema_error["message"]
class NetplanBackendException(NetplanException):
pass
class NetplanEmitterException(NetplanException):
pass
class NetplanFormatException(NetplanException):
pass
# Used in case the "domain" received from libnetplan doesn't exist
NETPLAN_EXCEPTIONS_FALLBACK = defaultdict(lambda: NetplanException)
# If a domain that doesn't exist is queried, it will fallback to NETPLAN_EXCEPTIONS_FALLBACK
# which will return NetplanException for any key accessed.
NETPLAN_EXCEPTIONS = defaultdict(lambda: NETPLAN_EXCEPTIONS_FALLBACK, {
NETPLAN_ERROR_DOMAINS.NETPLAN_PARSER_ERROR: {
NETPLAN_PARSER_ERRORS.NETPLAN_ERROR_INVALID_YAML: NetplanParserException,
NETPLAN_PARSER_ERRORS.NETPLAN_ERROR_INVALID_CONFIG: NetplanParserException,
},
NETPLAN_ERROR_DOMAINS.NETPLAN_VALIDATION_ERROR: {
NETPLAN_VALIDATION_ERRORS.NETPLAN_ERROR_CONFIG_GENERIC: NetplanException,
NETPLAN_VALIDATION_ERRORS.NETPLAN_ERROR_CONFIG_VALIDATION: NetplanValidationException,
},
# FILE_ERRORS are "errno" values and they all throw the same exception
NETPLAN_ERROR_DOMAINS.NETPLAN_FILE_ERROR: defaultdict(lambda: NetplanFileException),
NETPLAN_ERROR_DOMAINS.NETPLAN_BACKEND_ERROR: {
NETPLAN_BACKEND_ERRORS.NETPLAN_ERROR_UNSUPPORTED: NetplanBackendException,
NETPLAN_BACKEND_ERRORS.NETPLAN_ERROR_VALIDATION: NetplanBackendException,
},
NETPLAN_ERROR_DOMAINS.NETPLAN_EMITTER_ERROR: {
NETPLAN_EMITTER_ERRORS.NETPLAN_ERROR_YAML_EMITTER: NetplanEmitterException,
},
NETPLAN_ERROR_DOMAINS.NETPLAN_FORMAT_ERROR: {
NETPLAN_FORMAT_ERRORS.NETPLAN_ERROR_FORMAT_INVALID_YAML: NetplanFormatException,
}
})
def _checked_lib_call(fn, *args):
ref = ffi.new('NetplanError **')
ret = bool(fn(*args, ref))
if not ret:
err = ref[0]
if err == ffi.NULL: # pragma: nocover (should never happen)
raise NetplanException("Unknown error", 0, 0)
domain_code = lib.netplan_error_code(err)
error_domain = domain_code >> 32 # upper 32 bits
error_code = int(ffi.cast('uint32_t', domain_code)) # lower 32 bits
error_message = _string_realloc_call_no_error(lambda b: lib.netplan_error_message(err, b, len(b)))
exception = NETPLAN_EXCEPTIONS[error_domain][error_code]
raise exception(error_message, error_domain, error_code)
return ret
def _string_realloc_call_no_error(function: callable):
size = 16
while size < 1048576: # 1MB
buf = ffi.new('char[]', size)
code = function(buf)
if code == -2:
size = size * 2
continue
if code < 0: # pragma: nocover
raise NetplanException("Unknown error: %d" % code)
elif code == 0:
return None # pragma: nocover as it's hard to trigger for now
else:
return ffi.string(buf).decode('utf-8')
raise NetplanException('Halting due to string buffer size > 1M') # pragma: nocover
netplan-0.107.1/python-cffi/netplan/meson.build 0000664 0000000 0000000 00000002351 14536110317 0021354 0 ustar 00root root 0000000 0000000 pymod = import('python')
python = pymod.find_installation(
'python3',
modules: ['cffi']
)
python_dep = python.dependency(required: true)
cffi_srcs = configure_file(
command: [
python,
files('_build_cffi.py'),
join_paths(meson.project_source_root(), 'include'),
join_paths(meson.current_build_dir(), 'src'),
],
output: '_netplan_cffi.c',
)
# Generation of the Python binary extension through meson.
cffi_pyext = python.extension_module(
'_netplan_cffi',
cffi_srcs,
dependencies: [python_dep, glib, uuid],
include_directories: [inc, inc_internal],
link_with: [libnetplan],
subdir: 'netplan',
install: true,
)
bindings_sources = [
'__init__.py',
'netdef.py',
'parser.py',
'state.py',
'_utils.py']
# Copy module sources into build-dir,
# so they can be importet together with the binary extension
foreach src : bindings_sources
custom_target(
input: src,
output: src,
command: ['cp', '@INPUT@', join_paths(meson.current_build_dir(), '@PLAINNAME@')],
build_always_stale: true,
build_by_default: true,
depends: cffi_pyext)
endforeach
bindings = python.install_sources(
[bindings_sources],
subdir: 'netplan')
netplan-0.107.1/python-cffi/netplan/netdef.py 0000664 0000000 0000000 00000026523 14536110317 0021040 0 ustar 00root root 0000000 0000000 # Copyright (C) 2023 Canonical, Ltd.
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
from dataclasses import dataclass
from ._netplan_cffi import ffi, lib
from ._utils import _string_realloc_call_no_error, NetplanException
class NetDefinition():
def __init__(self, np_state, ptr):
self._ptr = ptr
# We hold on to this to avoid the underlying pointer being invalidated by
# the GC invoking netplan_state_free
self._parent = np_state
def __eq__(self, other: 'NetDefinition') -> bool:
if not hasattr(other, '_ptr'):
return False
return self._ptr == other._ptr
def _match_interface(self, iface_name: str = None, iface_driver: str = None, iface_mac: str = None) -> bool:
return bool(lib.netplan_netdef_match_interface(
self._ptr,
iface_name.encode('utf-8') if iface_name else ffi.NULL,
iface_mac.encode('utf-8') if iface_mac else ffi.NULL,
iface_driver.encode('utf-8') if iface_driver else ffi.NULL))
@property
def addresses(self) -> '_NetdefAddressIterator':
return _NetdefAddressIterator(self._ptr)
@property
def dhcp4(self) -> bool:
return bool(lib.netplan_netdef_get_dhcp4(self._ptr))
@property
def dhcp6(self) -> bool:
return bool(lib.netplan_netdef_get_dhcp6(self._ptr))
@property
def nameserver_addresses(self) -> '_NetdefNameserverIterator':
return _NetdefNameserverIterator(self._ptr)
@property
def nameserver_search(self) -> '_NetdefSearchDomainIterator':
return _NetdefSearchDomainIterator(self._ptr)
@property
def routes(self) -> '_NetdefRouteIterator':
return _NetdefRouteIterator(self._ptr)
@property
def macaddress(self) -> str:
return _string_realloc_call_no_error(lambda b: lib.netplan_netdef_get_macaddress(self._ptr, b, len(b)))
@property
def _has_match(self) -> bool:
return bool(lib.netplan_netdef_has_match(self._ptr))
@property
def set_name(self) -> str:
return _string_realloc_call_no_error(lambda b: lib.netplan_netdef_get_set_name(self._ptr, b, len(b)))
@property
def critical(self) -> bool:
return bool(lib._netplan_netdef_get_critical(self._ptr))
@property
def links(self) -> dict:
d = dict()
if sriov_link := lib.netplan_netdef_get_sriov_link(self._ptr):
d['sriov'] = NetDefinition(self._parent, sriov_link)
if vlan_link := lib.netplan_netdef_get_vlan_link(self._ptr):
d['vlan'] = NetDefinition(self._parent, vlan_link)
if bridge_link := lib.netplan_netdef_get_bridge_link(self._ptr):
d['bridge'] = NetDefinition(self._parent, bridge_link)
if bond_link := lib.netplan_netdef_get_bond_link(self._ptr):
d['bond'] = NetDefinition(self._parent, bond_link)
# TODO: ovs vs veth? Should we use the same field?
if peer_link := lib.netplan_netdef_get_peer_link(self._ptr):
d['peer'] = NetDefinition(self._parent, peer_link)
return d
@property
def _vlan_id(self) -> int:
vlan_id = lib._netplan_netdef_get_vlan_id(self._ptr)
if vlan_id == lib.UINT_MAX:
return None
return vlan_id
@property
def _has_sriov_vlan_filter(self) -> bool:
return bool(lib._netplan_netdef_get_sriov_vlan_filter(self._ptr))
@property
def backend(self) -> str:
return ffi.string(lib.netplan_backend_name(lib.netplan_netdef_get_backend(self._ptr))).decode('utf-8')
@property
def type(self) -> str:
return ffi.string(lib.netplan_def_type_name(lib.netplan_netdef_get_type(self._ptr))).decode('utf-8')
@property
def id(self) -> str:
return _string_realloc_call_no_error(lambda b: lib.netplan_netdef_get_id(self._ptr, b, len(b)))
@property
def filepath(self) -> str:
return _string_realloc_call_no_error(lambda b: lib.netplan_netdef_get_filepath(self._ptr, b, len(b)))
@property
def _embedded_switch_mode(self) -> str:
return _string_realloc_call_no_error(lambda b: lib._netplan_netdef_get_embedded_switch_mode(self._ptr, b, len(b)))
@property
def _delay_virtual_functions_rebind(self) -> bool:
return bool(lib.netplan_netdef_get_delay_virtual_functions_rebind(self._ptr))
@property
def _vf_count(self) -> int:
ref = ffi.new('NetplanError **')
count = lib._netplan_state_get_vf_count_for_def(self._parent._ptr, self._ptr, ref)
if count < 0:
err = ref[0]
msg = _string_realloc_call_no_error(lambda b: lib.netplan_error_message(err, b, len(b)))
raise NetplanException(msg)
return count
@property
def _is_trivial_compound_itf(self) -> bool:
'''
Returns True if the interface is a compound interface (bond or bridge),
and its configuration is trivial, without any variation from the defaults.
'''
return bool(lib._netplan_netdef_is_trivial_compound_itf(self._ptr))
class NetDefinitionIterator():
def __init__(self, np_state, dev_type: str = None):
# To keep things valid, keep a reference to the parent state
self.np_state = np_state
np_type = dev_type.encode('utf-8') if dev_type else ffi.NULL
self.iterator = lib._netplan_state_new_netdef_pertype_iter(np_state._ptr, np_type)
def __del__(self):
lib._netplan_netdef_pertype_iter_free(self.iterator)
def __iter__(self):
return self
def __next__(self):
next_value = lib._netplan_netdef_pertype_iter_next(self.iterator)
if not next_value:
raise StopIteration
return NetDefinition(self.np_state, next_value)
class NetplanAddress:
def __init__(self, address: str, lifetime: str, label: str):
self.address = address
self.lifetime = lifetime
self.label = label
def __str__(self) -> str:
return self.address
class _NetdefAddressIterator:
def __init__(self, netdef: NetDefinition):
self.netdef = netdef
self.iterator = lib._netplan_netdef_new_address_iter(netdef)
def __del__(self):
lib._netplan_address_iter_free(self.iterator)
def __iter__(self):
return self
def __next__(self):
next_value = lib._netplan_address_iter_next(self.iterator)
if not next_value:
raise StopIteration
content = next_value
# XXX: Introduce getters for .address/.lifetime/.label, to avoid
# exposing the 'address_iter' struct in _netplan_cffi.so
address = ffi.string(content.address).decode('utf-8') if content.address else None
lifetime = ffi.string(content.lifetime).decode('utf-8') if content.lifetime else None
label = ffi.string(content.label).decode('utf-8') if content.label else None
return NetplanAddress(address, lifetime, label)
class _NetdefNameserverIterator:
def __init__(self, netdef: NetDefinition):
self.netdef = netdef
self.iterator = lib._netplan_netdef_new_nameserver_iter(netdef)
def __del__(self):
lib._netplan_nameserver_iter_free(self.iterator)
def __iter__(self):
return self
def __next__(self):
next_value = lib._netplan_nameserver_iter_next(self.iterator)
if not next_value:
raise StopIteration
return ffi.string(next_value).decode('utf-8')
class _NetdefSearchDomainIterator:
def __init__(self, netdef):
self.netdef = netdef
self.iterator = lib._netplan_netdef_new_search_domain_iter(netdef)
def __del__(self):
lib._netplan_search_domain_iter_free(self.iterator)
def __iter__(self):
return self
def __next__(self):
next_value = lib._netplan_search_domain_iter_next(self.iterator)
if not next_value:
raise StopIteration
return ffi.string(next_value).decode('utf-8')
@dataclass(unsafe_hash=True)
class NetplanRoute:
_METRIC_UNSPEC_ = lib.UINT_MAX
_TABLE_UNSPEC_ = 0
to: str = None
via: str = None
from_addr: str = None
type: str = 'unicast'
scope: str = 'global'
protocol: str = None
table: int = _TABLE_UNSPEC_
family: int = -1
metric: int = _METRIC_UNSPEC_
mtubytes: int = 0
congestion_window: int = 0
advertised_receive_window: int = 0
onlink: bool = False
def __str__(self):
route = ""
if self.to:
route = route + self.to
if self.via:
route = route + f' via {self.via}'
if self.type:
route = route + f' type {self.type}'
if self.scope:
route = route + f' scope {self.scope}'
if self.from_addr:
route = route + f' src {self.from_addr}'
if self.metric < self._METRIC_UNSPEC_:
route = route + f' metric {self.metric}'
if self.table > self._TABLE_UNSPEC_:
route = route + f' table {self.table}'
return route.strip()
def to_dict(self):
route = {}
if self.family >= 0:
route['family'] = self.family
if self.to:
route['to'] = self.to
if self.via:
route['via'] = self.via
if self.from_addr:
route['from'] = self.from_addr
if self.metric < self._METRIC_UNSPEC_:
route['metric'] = self.metric
if self.table > self._TABLE_UNSPEC_:
route['table'] = self.table
route['type'] = self.type
return route
class _NetdefRouteIterator:
def __init__(self, netdef):
self.netdef = netdef
self.iterator = lib._netplan_netdef_new_route_iter(netdef)
def __del__(self):
lib._netplan_route_iter_free(self.iterator)
def __iter__(self):
return self
def __next__(self):
next_value = lib._netplan_route_iter_next(self.iterator)
if not next_value:
raise StopIteration
# The field 'from' happens to be a reserved keyword in Python
from_addr = getattr(next_value, 'from')
route = {
'to': ffi.string(next_value.to).decode('utf-8') if next_value.to else None,
'via': ffi.string(next_value.via).decode('utf-8') if next_value.via else None,
'from_addr': ffi.string(from_addr).decode('utf-8') if from_addr else None,
'type': ffi.string(next_value.type).decode('utf-8') if next_value.type else None,
'scope': ffi.string(next_value.scope).decode('utf-8') if next_value.scope else None,
'protocol': None,
'table': next_value.table,
'family': next_value.family,
'metric': next_value.metric,
'mtubytes': next_value.mtubytes,
'congestion_window': next_value.congestion_window,
'advertised_receive_window': next_value.advertised_receive_window,
'onlink': next_value.onlink
}
return NetplanRoute(**route)
netplan-0.107.1/python-cffi/netplan/parser.py 0000664 0000000 0000000 00000004074 14536110317 0021064 0 ustar 00root root 0000000 0000000 # Copyright (C) 2023 Canonical, Ltd.
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
from typing import Union, IO
from ._netplan_cffi import ffi, lib
from ._utils import _checked_lib_call
class Parser():
def __init__(self):
self._ptr = lib.netplan_parser_new()
def __del__(self):
ref = ffi.new('NetplanParser **', self._ptr)
lib.netplan_parser_clear(ref)
def load_yaml(self, input_file: Union[str, IO]):
if isinstance(input_file, str):
return _checked_lib_call(lib.netplan_parser_load_yaml, self._ptr, input_file.encode('utf-8'))
else:
return _checked_lib_call(lib.netplan_parser_load_yaml_from_fd, self._ptr, input_file.fileno())
def load_yaml_hierarchy(self, rootdir: str = None):
root = rootdir.encode('utf-8') if rootdir else ffi.NULL
return _checked_lib_call(lib.netplan_parser_load_yaml_hierarchy, self._ptr, root)
def load_keyfile(self, input_file: str): # TODO: load from File/fd (i.e. input_file: Union[str, IO])
return _checked_lib_call(lib.netplan_parser_load_keyfile, self._ptr, input_file.encode('utf-8'))
def load_nullable_fields(self, input_file: IO):
return _checked_lib_call(lib.netplan_parser_load_nullable_fields, self._ptr, input_file.fileno())
def _load_nullable_overrides(self, input_file: IO, constraint: str):
return _checked_lib_call(lib.netplan_parser_load_nullable_overrides,
self._ptr, input_file.fileno(), constraint.encode('utf-8'))
netplan-0.107.1/python-cffi/netplan/state.py 0000664 0000000 0000000 00000011711 14536110317 0020704 0 ustar 00root root 0000000 0000000 # Copyright (C) 2023 Canonical, Ltd.
# Author: Lukas Märdian
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
# from enum import IntEnum
from io import StringIO
import os
from typing import IO
from ._netplan_cffi import ffi, lib
from .netdef import NetDefinition, NetDefinitionIterator
from .parser import Parser
from ._utils import _checked_lib_call
# class NETPLAN_STORAGE(IntEnum):
# ETC = 0
# RUN = 1
# LIB = 2
class State():
def __init__(self):
self._ptr = lib.netplan_state_new()
def __del__(self):
ref = ffi.new('NetplanState **', self._ptr)
lib.netplan_state_clear(ref)
def __getitem__(self, netdef_id: str):
ptr = lib.netplan_state_get_netdef(self._ptr, netdef_id.encode('utf-8'))
if not ptr:
raise IndexError()
return NetDefinition(self, ptr)
def __len__(self):
return lib.netplan_state_get_netdefs_size(self._ptr)
def import_parser_results(self, parser: Parser):
_checked_lib_call(lib.netplan_state_import_parser_results, self._ptr, parser._ptr)
# def write_yaml(filter: str, default_filename: str = None,
# storage: NETPLAN_STORAGE = NETPLAN_STORAGE.ETC, rootdir str = None):
# # TODO: https://bugs.launchpad.net/netplan/+bug/2003727
# raise NotImplementedError
def _write_yaml_file(self, filename: str = None, rootdir: str = None):
name = filename.encode('utf-8') if filename else ffi.NULL
root = rootdir.encode('utf-8') if rootdir else ffi.NULL
_checked_lib_call(lib.netplan_state_write_yaml_file, self._ptr, name, root)
def _update_yaml_hierarchy(self, default_filename: str, rootdir: str = None):
name = default_filename.encode('utf-8')
root = rootdir.encode('utf-8') if rootdir else ffi.NULL
_checked_lib_call(lib.netplan_state_update_yaml_hierarchy, self._ptr, name, root)
def _dump_yaml(self, output_file: IO):
if isinstance(output_file, StringIO):
fd = os.memfd_create(name='netplan_temp_file')
_checked_lib_call(lib.netplan_state_dump_yaml, self._ptr, fd)
size = os.lseek(fd, 0, os.SEEK_CUR)
os.lseek(fd, 0, os.SEEK_SET)
data = os.read(fd, size)
os.close(fd)
output_file.write(data.decode('utf-8'))
else:
fd = output_file.fileno()
_checked_lib_call(lib.netplan_state_dump_yaml, self._ptr, fd)
@property
def backend(self) -> str:
return ffi.string(lib.netplan_backend_name(lib.netplan_state_get_backend(self._ptr))).decode('utf-8')
@property
def netdefs(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, None))
@property
def ethernets(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "ethernets"))
@property
def modems(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "modems"))
@property
def wifis(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "wifis"))
@property
def vlans(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "vlans"))
@property
def bridges(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "bridges"))
@property
def bonds(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "bonds"))
@property
def dummy_devices(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "dummy-devices"))
@property
def tunnels(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "tunnels"))
@property
def virtual_ethernets(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "virtual-ethernets"))
@property
def vrfs(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "vrfs"))
@property
def ovs_ports(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "_ovs-ports"))
@property
def nm_devices(self) -> NetDefinitionIterator:
return dict((nd.id, nd) for nd in NetDefinitionIterator(self, "nm-devices"))
netplan-0.107.1/rpm/ 0000775 0000000 0000000 00000000000 14536110317 0014120 5 ustar 00root root 0000000 0000000 netplan-0.107.1/rpm/netplan.spec 0000664 0000000 0000000 00000022210 14536110317 0016432 0 ustar 00root root 0000000 0000000 # Ubuntu calls their own software netplan.io in the archive due to name conflicts
%global ubuntu_name netplan.io
# If this isn't defined, define it
%{?!_systemdgeneratordir:%global _systemdgeneratordir /usr/lib/systemd/system-generators}
# Netplan library soversion major
%global libsomajor 0.0
# networkd is not available everywhere
%if 0%{?rhel}
%bcond_with networkd_support
%else
%bcond_without networkd_support
%endif
Name: netplan
Version: 0.106
Release: 0%{?dist}
Summary: Network configuration tool using YAML
Group: System Environment/Base
License: GPL-3.0-only
URL: http://netplan.io/
Source0: https://github.com/canonical/%{name}/archive/%{version}/%{name}-%{version}.tar.gz
BuildRequires: gcc
BuildRequires: meson >= 0.61
BuildRequires: pkgconfig(bash-completion)
BuildRequires: pkgconfig(glib-2.0)
BuildRequires: pkgconfig(gio-2.0)
BuildRequires: pkgconfig(libsystemd)
BuildRequires: pkgconfig(systemd)
BuildRequires: pkgconfig(yaml-0.1)
BuildRequires: pkgconfig(uuid)
BuildRequires: python3-devel
BuildRequires: python3-cffi
BuildRequires: systemd-rpm-macros
BuildRequires: %{_bindir}/pandoc
BuildRequires: %{_bindir}/find
# For tests
BuildRequires: %{_sbindir}/ip
BuildRequires: pkgconfig(cmocka)
BuildRequires: python3dist(coverage)
BuildRequires: dbus-x11
BuildRequires: python3dist(netifaces)
BuildRequires: python3dist(pycodestyle)
BuildRequires: python3dist(pyflakes)
BuildRequires: python3dist(pytest)
BuildRequires: python3dist(pytest-cov)
BuildRequires: python3dist(pyyaml)
BuildRequires: python3dist(rich)
BuildRequires: %{_bindir}/ovs-vsctl
# /usr/sbin/netplan is a Python 3 script that requires Python modules
Requires: python3dist(netifaces)
Requires: python3dist(pyyaml)
Requires: python3dist(rich)
# 'ip' command is used in netplan apply subcommand
Requires: %{_sbindir}/ip
# netplan ships dbus files
Requires: dbus-common
# Netplan requires a backend for configuration
Requires: %{name}-default-backend
# Prefer NetworkManager
Suggests: %{name}-default-backend-NetworkManager
# Netplan requires its core libraries
Requires: %{name}-libs%{?_isa} = %{version}-%{release}
# Provide the package name that Ubuntu uses for it too...
Provides: %{ubuntu_name} = %{version}-%{release}
Provides: %{ubuntu_name}%{?_isa} = %{version}-%{release}
%description
netplan reads network configuration from /etc/netplan/*.yaml which are written by administrators,
installers, cloud image instantiations, or other OS deployments. During early boot, it generates
backend specific configuration files in /run to hand off control of devices to a particular
networking daemon.
Currently supported backends are NetworkManager and systemd-networkd.
%files
%license COPYING
%doc %{_docdir}/%{name}/
%{_sbindir}/%{name}
%{_datadir}/%{name}/
%{_datadir}/dbus-1/system-services/io.netplan.Netplan.service
%{_datadir}/dbus-1/system.d/io.netplan.Netplan.conf
%{_systemdgeneratordir}/%{name}
%{_mandir}/man5/%{name}.5*
%{_mandir}/man8/%{name}*.8*
%dir %{_sysconfdir}/%{name}
%dir %{_prefix}/lib/%{name}
%{_libexecdir}/%{name}/
%{_datadir}/bash-completion/completions/%{name}
%{python3_sitelib}/%{name}/
%{python3_sitearch}/%{name}/
# ------------------------------------------------------------------------------------------------
%package libs
Summary: Network configuration tool using YAML (core library)
Group: System Environment/Libraries
%description libs
netplan reads network configuration from /etc/netplan/*.yaml which are written by administrators,
installers, cloud image instantiations, or other OS deployments. During early boot, it generates
backend specific configuration files in /run to hand off control of devices to a particular
networking daemon.
This package provides Netplan's core libraries.
%files libs
%license COPYING
%{_libdir}/libnetplan.so.%{libsomajor}{,.*}
# ------------------------------------------------------------------------------------------------
%package devel
Summary: Network configuration tool using YAML (development files)
Group: Development/Libraries
Requires: %{name}-libs%{?_isa} = %{version}-%{release}
%description devel
netplan reads network configuration from /etc/netplan/*.yaml which are written by administrators,
installers, cloud image instantiations, or other OS deployments. During early boot, it generates
backend specific configuration files in /run to hand off control of devices to a particular
networking daemon.
This package provides development headers and libraries for building applications using Netplan.
%files devel
%{_includedir}/%{name}/
%{_libdir}/libnetplan.so
%{_libdir}/pkgconfig/%{name}.pc
# ------------------------------------------------------------------------------------------------
%package default-backend-NetworkManager
Summary: Network configuration tool using YAML (NetworkManager backend)
Group: System Environment/Base
Requires: %{name} = %{version}-%{release}
# Netplan requires NetworkManager for configuration
Requires: NetworkManager
# Disable NetworkManager's autoconfiguration
Requires: NetworkManager-config-server
# Generally, if linux-firmware-whence is installed, we want Wi-Fi capabilities
Recommends: (NetworkManager-wifi if linux-firmware-whence)
Suggests: NetworkManager-wifi
# One and only one default backend permitted
Conflicts: %{name}-default-backend
Provides: %{name}-default-backend
BuildArch: noarch
%description default-backend-NetworkManager
netplan reads network configuration from /etc/netplan/*.yaml which are written by administrators,
installers, cloud image instantiations, or other OS deployments. During early boot, it generates
backend specific configuration files in /run to hand off control of devices to a particular
networking daemon.
This package configures Netplan to use NetworkManager as its backend.
%files default-backend-NetworkManager
%{_prefix}/lib/%{name}/00-netplan-default-renderer-nm.yaml
# ------------------------------------------------------------------------------------------------
%if %{with networkd_support}
%package default-backend-networkd
Summary: Network configuration tool using YAML (systemd-networkd backend)
Group: System Environment/Base
Requires: %{name} = %{version}-%{release}
# Netplan requires systemd-networkd for configuration
Requires: systemd-networkd
# Generally, if linux-firmware-whence is installed, we want Wi-Fi capabilities
Recommends: (wpa_supplicant if linux-firmware-whence)
Suggests: wpa_supplicant
# One and only one default backend permitted
Conflicts: %{name}-default-backend
Provides: %{name}-default-backend
BuildArch: noarch
%description default-backend-networkd
netplan reads network configuration from /etc/netplan/*.yaml which are written by administrators,
installers, cloud image instantiations, or other OS deployments. During early boot, it generates
backend specific configuration files in /run to hand off control of devices to a particular
networking daemon.
This package configures Netplan to use systemd-networkd as its backend.
%files default-backend-networkd
%{_prefix}/lib/%{name}/00-netplan-default-renderer-networkd.yaml
%endif
# ------------------------------------------------------------------------------------------------
%prep
%autosetup -p1
# Drop -Werror to avoid the following error:
# /usr/include/glib-2.0/glib/glib-autocleanups.h:28:3: error: 'ip_str' may be used uninitialized in this function [-Werror=maybe-uninitialized]
sed -e "s/werror=true/werror=false/g" -i meson.build
%build
%meson
%meson_build
%install
%meson_install
# Remove useless "compat" symlink and path
rm -f %{buildroot}/lib/netplan/generate
rmdir %{buildroot}/lib/netplan
rmdir %{buildroot}/lib
# Remove superfluous __pycache__
rm -rf %{buildroot}/usr/lib/python3.11/site-packages/netplan/__pycache__
# Pre-create the config directories
mkdir -p %{buildroot}%{_sysconfdir}/%{name}
mkdir -p %{buildroot}%{_prefix}/lib/%{name}
# Generate Netplan default renderer configuration
cat > %{buildroot}%{_prefix}/lib/%{name}/00-netplan-default-renderer-nm.yaml < %{buildroot}%{_prefix}/lib/%{name}/00-netplan-default-renderer-networkd.yaml < - 0.106-0
- Update to 0.106
- Resync with Fedora spec
- Drop EL7 and EL8 support
* Thu Aug 18 2022 Lukas Märdian - 0.105-0
- Update to 0.105
* Sun Feb 20 2022 Neal Gompa - 0.104-0
- Update to 0.104
- Resync with Fedora spec
* Fri Dec 14 2018 Mathieu Trudel-Lapierre - 0.95
- Update to 0.95
* Sat Oct 13 2018 Neal Gompa - 0.40.3-0
- Rebase to 0.40.3
* Tue Mar 13 2018 Neal Gompa - 0.34-0.1
- Update to 0.34
* Wed Mar 7 2018 Neal Gompa - 0.33-0.1
- Rebase to 0.33
* Sat Nov 4 2017 Neal Gompa - 0.30-1
- Rebase to 0.30
* Sun Jul 2 2017 Neal Gompa - 0.23~17.04.1-1
- Initial packaging
netplan-0.107.1/sitecustomize.py 0000664 0000000 0000000 00000000053 14536110317 0016601 0 ustar 00root root 0000000 0000000 import coverage
coverage.process_startup()
netplan-0.107.1/snap/ 0000775 0000000 0000000 00000000000 14536110317 0014263 5 ustar 00root root 0000000 0000000 netplan-0.107.1/snap/snapcraft.yaml 0000664 0000000 0000000 00000002106 14536110317 0017127 0 ustar 00root root 0000000 0000000 name: netplan
version: git
summary: Backend-agnostic network configuration in YAML
description: |
Netplan is a utility for easily configuring networking on a linux system.
You simply create a YAML description of the required network interfaces and
what each should be configured to do. From this description Netplan will
generate all the necessary configuration for your chosen renderer tool.
grade: devel
confinement: classic
apps:
netplan:
command: usr/sbin/netplan
environment:
PYTHONPATH: $PYTHONPATH:$SNAP/usr/lib/python3/dist-packages
parts:
netplan:
source: https://github.com/canonical/netplan.git
plugin: make
build-packages:
- bash-completion
- libglib2.0-dev
- libyaml-dev
- uuid-dev
- pandoc
- pkg-config
- python3
- python3-coverage
- python3-yaml
- python3-netifaces
- python3-pytest
- python3-pytest-cov
- pyflakes3
- pep8
- systemd
stage-packages:
- iproute2
- python3
- python3-netifaces
- python3-yaml
- systemd
netplan-0.107.1/spread.yaml 0000664 0000000 0000000 00000003260 14536110317 0015465 0 ustar 00root root 0000000 0000000 project: netplan
backends:
lxd:
systems: [ubuntu-22.04]
qemu:
systems:
- ubuntu-22.04-64:
username: ubuntu
password: ubuntu
suites:
tests/spread/:
summary: integration tests
path: /home/tests
prepare: |
# FIXME: remove after legacy symlink in 107 gets dropped (see meson.build)
# This is needed as the git netplan puts a symlink to /lib/netplan/generate
# but the package has the real generate there
rm -f /lib/netplan/generate
# FIXME: having the debian packging available would allow "apt
# build-dep -y ./" would make this easier :)
apt update -qq
apt install -y build-essential meson pkg-config libyaml-dev \
libglib2.0-dev uuid-dev python3 libsystemd-dev pandoc python3-pytest \
python3-coverage libcmocka-dev python3-rich python3-cffi libpython3-dev
# install, a bit ugly but this is a container (did I mention the packaging?)
meson setup build --prefix=/usr
meson compile -C build
# FIXME: enable, this crashes right now with:
# https://paste.ubuntu.com/p/qRnJvjyddN/
#meson test -C build --verbose
rm -rf /usr/share/netplan/netplan # clear (old) system installation
meson install -C build --destdir=/
# set some defaults
cat > /etc/netplan/0-snapd-defaults.yaml <<'EOF'
network:
version: 2
bridges:
br54:
dhcp4: true
EOF
chmod 0600 /etc/netplan/0-snapd-defaults.yaml
echo "Precondition check, the basics work"
netplan get bridges.br54.dhcp4 | MATCH true
# keep original config around
tar cvf "$SPREAD_PATH"/etc-netplan.tar.gz /etc/netplan/
restore-each: |
# restore original netplan dir
rm -rf /etc/netplan/*
(cd / && tar xvf "$SPREAD_PATH"/etc-netplan.tar.gz)
netplan-0.107.1/src/ 0000775 0000000 0000000 00000000000 14536110317 0014111 5 ustar 00root root 0000000 0000000 netplan-0.107.1/src/abi.h 0000664 0000000 0000000 00000026465 14536110317 0015032 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2022 Canonical, Ltd.
* Author: Lukas Märdian
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#pragma once
#include
#include
#include
typedef int NetplanFlags;
/* Those types are part of our ABI as they have been exposed in older versions */
typedef enum {
NETPLAN_OPTIONAL_IPV4_LL = 1<<0,
NETPLAN_OPTIONAL_IPV6_RA = 1<<1,
NETPLAN_OPTIONAL_DHCP4 = 1<<2,
NETPLAN_OPTIONAL_DHCP6 = 1<<3,
NETPLAN_OPTIONAL_STATIC = 1<<4,
} NetplanOptionalAddressFlag;
/* Fields below are valid for dhcp4 and dhcp6 unless otherwise noted. */
typedef struct dhcp_overrides {
gboolean use_dns;
gboolean use_ntp;
gboolean send_hostname;
gboolean use_hostname;
gboolean use_mtu;
gboolean use_routes;
char* use_domains; /* netplan-feature: dhcp-use-domains */
char* hostname;
guint metric;
} NetplanDHCPOverrides;
typedef enum {
NETPLAN_RA_MODE_KERNEL,
NETPLAN_RA_MODE_ENABLED,
NETPLAN_RA_MODE_DISABLED,
} NetplanRAMode;
typedef enum {
NETPLAN_IB_MODE_KERNEL,
NETPLAN_IB_MODE_DATAGRAM,
NETPLAN_IB_MODE_CONNECTED,
NETPLAN_IB_MODE_MAX_,
} NetplanInfinibandMode;
typedef enum {
NETPLAN_WIFI_WOWLAN_DEFAULT = 1<<0,
NETPLAN_WIFI_WOWLAN_ANY = 1<<1,
NETPLAN_WIFI_WOWLAN_DISCONNECT = 1<<2,
NETPLAN_WIFI_WOWLAN_MAGIC = 1<<3,
NETPLAN_WIFI_WOWLAN_GTK_REKEY_FAILURE = 1<<4,
NETPLAN_WIFI_WOWLAN_EAP_IDENTITY_REQ = 1<<5,
NETPLAN_WIFI_WOWLAN_4WAY_HANDSHAKE = 1<<6,
NETPLAN_WIFI_WOWLAN_RFKILL_RELEASE = 1<<7,
NETPLAN_WIFI_WOWLAN_TCP = 1<<8,
} NetplanWifiWowlanFlag;
struct NetplanWifiWowlanType {
char* name;
NetplanWifiWowlanFlag flag;
};
/* Tunnel mode enum; sync with NetworkManager's DBUS API */
/* TODO: figure out whether networkd's GRETAP and NM's ISATAP
* are the same thing.
*/
typedef enum {
NETPLAN_TUNNEL_MODE_UNKNOWN = 0,
NETPLAN_TUNNEL_MODE_IPIP = 1,
NETPLAN_TUNNEL_MODE_GRE = 2,
NETPLAN_TUNNEL_MODE_SIT = 3,
NETPLAN_TUNNEL_MODE_ISATAP = 4, // NM only.
NETPLAN_TUNNEL_MODE_VTI = 5,
NETPLAN_TUNNEL_MODE_IP6IP6 = 6,
NETPLAN_TUNNEL_MODE_IPIP6 = 7,
NETPLAN_TUNNEL_MODE_IP6GRE = 8,
NETPLAN_TUNNEL_MODE_VTI6 = 9,
NETPLAN_TUNNEL_MODE_GRETAP = 10,
NETPLAN_TUNNEL_MODE_IP6GRETAP = 11,
/* "ip-tunnel" modes supported by Network Manager end here */
NETPLAN_TUNNEL_MODE_NM_MAX = 12,
NETPLAN_TUNNEL_MODE_VXLAN = 100,
NETPLAN_TUNNEL_MODE_WIREGUARD = 101,
NETPLAN_TUNNEL_MODE_MAX_,
} NetplanTunnelMode;
typedef enum {
NETPLAN_AUTH_KEY_MANAGEMENT_NONE,
NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK,
NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP,
NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSHA256,
NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSUITE_B_192,
NETPLAN_AUTH_KEY_MANAGEMENT_8021X,
NETPLAN_AUTH_KEY_MANAGEMENT_WPA_SAE,
NETPLAN_AUTH_KEY_MANAGEMENT_MAX,
} NetplanAuthKeyManagementType;
typedef enum {
NETPLAN_AUTH_EAP_NONE,
NETPLAN_AUTH_EAP_TLS,
NETPLAN_AUTH_EAP_PEAP,
NETPLAN_AUTH_EAP_TTLS,
NETPLAN_AUTH_EAP_LEAP,
NETPLAN_AUTH_EAP_PWD,
NETPLAN_AUTH_EAP_UNKNOWN,
NETPLAN_AUTH_EAP_METHOD_MAX,
} NetplanAuthEAPMethod;
typedef enum {
NETPLAN_AUTH_PMF_MODE_NONE,
NETPLAN_AUTH_PMF_MODE_DISABLED,
NETPLAN_AUTH_PMF_MODE_OPTIONAL,
NETPLAN_AUTH_PMF_MODE_REQUIRED,
} NetplanAuthPMFMode;
typedef struct authentication_settings {
NetplanAuthKeyManagementType key_management;
NetplanAuthEAPMethod eap_method;
NetplanAuthPMFMode pmf_mode;
char* identity;
char* anonymous_identity;
char* password;
char* ca_certificate;
char* client_certificate;
char* client_key;
char* client_key_password;
char* phase2_auth; /* netplan-feature: auth-phase2 */
char* psk;
} NetplanAuthenticationSettings;
typedef enum {
NETPLAN_KEY_FLAG_NONE = 0,
NETPLAN_KEY_FLAG_AGENT_OWNED = 1<<0,
NETPLAN_KEY_FLAG_NOT_SAVED = 1<<1,
NETPLAN_KEY_FLAG_NOT_REQUIRED = 1<<2,
NETPLAN_KEY_FLAG_MAX_
} NetplanKeyFlags;
typedef struct ovs_controller {
char* connection_mode;
GArray* addresses;
} NetplanOVSController;
typedef struct ovs_settings {
GHashTable* external_ids;
GHashTable* other_config;
char* lacp;
char* fail_mode;
gboolean mcast_snooping;
GArray* protocols;
gboolean rstp;
NetplanOVSController controller;
NetplanAuthenticationSettings ssl;
} NetplanOVSSettings;
typedef struct netplan_backend_settings {
char *name;
char *uuid;
char *stable_id;
char *device;
GData* passthrough; /* See g_datalist* functions */
} NetplanBackendSettings;
typedef enum
{
/**
* @brief Tristate enum type
*
* This type defines a boolean which can be unset, i.e.
* this type has three states. The enum is ordered so
* that
*
* UNSET -> -1
* FALSE -> 0
* TRUE -> 1
*
* And the integer values can be used directly when
* converting to string.
*/
NETPLAN_TRISTATE_UNSET = -1, /* -1 */
NETPLAN_TRISTATE_FALSE, /* 0 */
NETPLAN_TRISTATE_TRUE, /* 1 */
} NetplanTristate;
typedef struct netplan_vxlan NetplanVxlan;
/* Keep 'struct netplan_net_definition' in a separate header file, to allow for
* abidiff to consider it "public API" (although it isn't) and notify us about
* ABI compatibility issues. */
struct netplan_net_definition {
NetplanDefType type;
NetplanBackend backend;
char* id;
/* only necessary for NetworkManager connection UUIDs in some cases */
uuid_t uuid;
/* status options */
gboolean optional;
NetplanOptionalAddressFlag optional_addresses;
gboolean critical;
/* addresses */
gboolean dhcp4;
gboolean dhcp6;
char* dhcp_identifier;
NetplanDHCPOverrides dhcp4_overrides;
NetplanDHCPOverrides dhcp6_overrides;
NetplanRAMode accept_ra;
GArray* ip4_addresses;
GArray* ip6_addresses;
GArray* address_options;
gboolean ip6_privacy;
guint ip6_addr_gen_mode;
char* ip6_addr_gen_token;
char* gateway4;
char* gateway6;
GArray* ip4_nameservers;
GArray* ip6_nameservers;
GArray* search_domains;
GArray* routes;
GArray* ip_rules;
GArray* wireguard_peers;
struct {
gboolean ipv4;
gboolean ipv6;
} linklocal;
/* primary ID for member devices */
char* bridge; // deprecated, use bridge_link instead
char* bond; // deprecated, use bond_link instead
/* peer ID for OVS patch ports */
char* peer; // deprecated, use peer_link instead
/* vlan */
guint vlan_id;
NetplanNetDefinition* vlan_link;
gboolean has_vlans;
/* Configured custom MAC address */
char* set_mac;
/* interface mtu */
guint mtubytes;
/* ipv6 mtu */
/* netplan-feature: ipv6-mtu */
guint ipv6_mtubytes;
/* these properties are only valid for physical interfaces (type < ND_VIRTUAL) */
char* set_name;
struct {
/* A glob (or tab-separated list of globs) to match a specific driver */
char* driver;
char* mac;
char* original_name;
} match;
gboolean has_match;
gboolean wake_on_lan;
gint wowlan;
gboolean emit_lldp;
/* these properties are only valid for NETPLAN_DEF_TYPE_WIFI */
GHashTable* access_points; /* SSID → NetplanWifiAccessPoint* */
struct {
char* mode;
char* lacp_rate;
char* monitor_interval;
guint min_links;
char* transmit_hash_policy;
char* selection_logic;
gboolean all_members_active;
char* arp_interval;
GArray* arp_ip_targets;
char* arp_validate;
char* arp_all_targets;
char* up_delay;
char* down_delay;
char* fail_over_mac_policy;
guint gratuitous_arp;
/* TODO: unsolicited_na */
guint packets_per_member;
char* primary_reselect_policy;
guint resend_igmp;
char* learn_interval;
char* primary_member;
} bond_params;
/* netplan-feature: modems */
struct {
char* apn;
gboolean auto_config;
char* device_id;
char* network_id;
char* number;
char* password;
char* pin;
char* sim_id;
char* sim_operator_id;
char* username;
} modem_params;
struct {
char* ageing_time;
guint priority;
guint port_priority;
char* forward_delay;
char* hello_time;
char* max_age;
guint path_cost;
gboolean stp;
} bridge_params;
gboolean custom_bridging;
struct {
NetplanTunnelMode mode;
char *local_ip;
char *remote_ip;
char *input_key;
char *output_key;
char *private_key; /* used for wireguard */
guint fwmark;
guint port;
} tunnel;
NetplanAuthenticationSettings auth;
gboolean has_auth;
/* these properties are only valid for SR-IOV NICs */
/* netplan-feature: sriov */
NetplanNetDefinition* sriov_link;
gboolean sriov_vlan_filter;
guint sriov_explicit_vf_count;
/* these properties are only valid for OpenVSwitch */
/* netplan-feature: openvswitch */
NetplanOVSSettings ovs_settings;
NetplanBackendSettings backend_settings;
char* filepath;
/* it cannot be in the tunnel struct: https://github.com/canonical/netplan/pull/206 */
guint tunnel_ttl;
/* netplan-feature: activation-mode */
char* activation_mode;
/* configure without carrier */
gboolean ignore_carrier;
/* offload options */
NetplanTristate receive_checksum_offload;
NetplanTristate transmit_checksum_offload;
NetplanTristate tcp_segmentation_offload;
NetplanTristate tcp6_segmentation_offload;
NetplanTristate generic_segmentation_offload;
NetplanTristate generic_receive_offload;
NetplanTristate large_receive_offload;
struct private_netdef_data* _private;
/* netplan-feature: eswitch-mode */
char* embedded_switch_mode;
gboolean sriov_delay_virtual_functions_rebind;
/* netplan-feature: infiniband */
NetplanInfinibandMode ib_mode; /* IPoIB */
/* netplan-feature: regdom */
char* regulatory_domain;
/* vrf */
/* netplan-feature: vrf */
NetplanNetDefinition* vrf_link;
guint vrf_table;
NetplanTristate bridge_neigh_suppress;
/* vxlan */
/* netplan-feature: vxlan */
gboolean has_vxlans;
NetplanVxlan* vxlan;
NetplanNetDefinition* bridge_link;
NetplanNetDefinition* bond_link;
NetplanNetDefinition* peer_link;
/* True if "networkmanager" settings are present */
gboolean has_backend_settings_nm;
guint tunnel_private_key_flags;
/* virtual-ethernet */
/* netplan-feature: virtual-ethernet */
NetplanNetDefinition* veth_peer_link;
};
netplan-0.107.1/src/abi_compat.c 0000664 0000000 0000000 00000017600 14536110317 0016357 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Canonical, Ltd.
* Author: Simon Chopin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
/*
* The whole point of this file is to export the former ABI as simple wrappers
* around the newer API. Most functions should thus be relatively short, the meat
* of things being in the newer API implementation.
*/
#include "netplan.h"
#include "types-internal.h"
#include "util-internal.h"
#include "parse-nm.h"
#include "parse-globals.h"
#include "names.h"
#include "networkd.h"
#include "nm.h"
#include "sriov.h"
#include "openvswitch.h"
#include "util.h"
#include
#include
#include
#include
#include
/* These arrays are not useful per-say, but allow us to export the various
* struct offsets of the netplan_state members to the linker, which can use
* them in a linker script to create symbols pointing to the internal data
* members of the global_state global object.
*/
/* The +8 is to prevent the compiler removing the array if the array is empty,
* i.e. the data member is the first in the struct definition.
*/
__attribute__((used)) __attribute__((section("netdefs_offset")))
char _netdefs_off[8+offsetof(struct netplan_state, netdefs)] = {};
__attribute__((used)) __attribute__((section("netdefs_ordered_offset")))
char _netdefs_ordered_off[8+offsetof(struct netplan_state, netdefs_ordered)] = {};
__attribute__((used)) __attribute__((section("ovs_settings_offset")))
char _ovs_settings_global_off[8+offsetof(struct netplan_state, ovs_settings)] = {};
__attribute__((used)) __attribute__((section("global_backend_offset")))
char _global_backend_off[8+offsetof(struct netplan_state, backend)] = {};
NETPLAN_ABI
NetplanState global_state = {};
// LCOV_EXCL_START
NetplanBackend
netplan_get_global_backend()
{
return netplan_state_get_backend(&global_state);
}
// LCOV_EXCL_STOP
/**
* Clear NetplanNetDefinition hashtable
*/
// LCOV_EXCL_START
guint
netplan_clear_netdefs()
{
guint n = netplan_state_get_netdefs_size(&global_state);
netplan_state_reset(&global_state);
netplan_parser_reset(&global_parser);
return n;
}
// LCOV_EXCL_STOP
gboolean
netplan_parse_yaml(const char* filename, GError** error)
{
return netplan_parser_load_yaml(&global_parser, filename, error);
}
/**
* Post-processing after parsing all config files
*/
GHashTable *
netplan_finish_parse(GError** error)
{
if (netplan_state_import_parser_results(&global_state, &global_parser, error))
return global_state.netdefs;
return NULL; // LCOV_EXCL_LINE
}
/**
* Generate the Netplan YAML configuration for the selected netdef
* @def: NetplanNetDefinition (as pointer), the data to be serialized
* @rootdir: If not %NULL, generate configuration in this root directory
* (useful for testing).
*/
void
write_netplan_conf(const NetplanNetDefinition* def, const char* rootdir)
{
netplan_netdef_write_yaml(&global_state, def, rootdir, NULL);
}
/**
* Generate the Netplan YAML configuration for all currently parsed netdefs
* @file_hint: Name hint for the generated output YAML file
* @rootdir: If not %NULL, generate configuration in this root directory
* (useful for testing).
*/
NETPLAN_ABI void
write_netplan_conf_full(const char* file_hint, const char* rootdir)
{
g_autofree gchar *path = NULL;
int fd = -1;
netplan_finish_parse(NULL);
if (!netplan_state_has_nondefault_globals(&global_state) &&
!netplan_state_get_netdefs_size(&global_state))
return;
path = g_build_path(G_DIR_SEPARATOR_S, rootdir ?: G_DIR_SEPARATOR_S, "etc", "netplan", file_hint, NULL);
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd >= 0) {
netplan_state_dump_yaml(&global_state, fd, NULL);
close(fd);
} else
g_warning("write_netplan_conf_full: failed to open %s.", path); // LCOV_EXCL_LINE
}
NETPLAN_PUBLIC gboolean
netplan_parse_keyfile(const char* filename, GError** error)
{
return netplan_parser_load_keyfile(&global_parser, filename, error);
}
// LCOV_EXCL_START
void process_input_file(const char *f)
{
GError* error = NULL;
g_debug("Processing input file %s..", f);
if (!netplan_parser_load_yaml(&global_parser, f, &error)) {
g_fprintf(stderr, "%s\n", error->message);
exit(1);
}
}
gboolean
process_yaml_hierarchy(const char* rootdir)
{
GError* error = NULL;
if (!netplan_parser_load_yaml_hierarchy(&global_parser, rootdir, &error)) {
g_fprintf(stderr, "%s\n", error->message);
exit(1);
}
return TRUE;
}
// LCOV_EXCL_STOP
/**
* Helper function for testing only
*/
NETPLAN_INTERNAL void
_write_netplan_conf(const char* netdef_id, const char* rootdir)
{
GHashTable* ht = NULL;
const NetplanNetDefinition* def = NULL;
ht = netplan_finish_parse(NULL);
def = g_hash_table_lookup(ht, netdef_id);
if (def)
write_netplan_conf(def, rootdir);
else
g_warning("_write_netplan_conf: netdef_id (%s) not found.", netdef_id); // LCOV_EXCL_LINE
}
// LCOV_EXCL_START
/**
* Get the filename from which the given netdef has been parsed.
* @rootdir: ID of the netdef to be looked up
* @rootdir: parse files from this root directory
*/
gchar*
netplan_get_filename_by_id(const char* netdef_id, const char* rootdir)
{
NetplanParser* npp = netplan_parser_new();
NetplanState* np_state = netplan_state_new();
char *filepath = NULL;
GError* error = NULL;
if (!netplan_parser_load_yaml_hierarchy(npp, rootdir, &error) ||
!netplan_state_import_parser_results(np_state, npp, &error)) {
g_fprintf(stderr, "%s\n", error->message);
netplan_parser_clear(&npp);
netplan_state_clear(&np_state);
return NULL;
}
netplan_parser_clear(&npp);
NetplanNetDefinition* netdef = netplan_state_get_netdef(np_state, netdef_id);
if (netdef)
filepath = g_strdup(netdef->filepath);
netplan_state_clear(&np_state);
return filepath;
}
NETPLAN_INTERNAL struct netdef_pertype_iter*
_netplan_state_new_netdef_pertype_iter(NetplanState* np_state, const char* devtype);
NETPLAN_INTERNAL struct netdef_pertype_iter*
_netplan_iter_defs_per_devtype_init(const char *devtype)
{
return _netplan_state_new_netdef_pertype_iter(&global_state, devtype);
}
NETPLAN_INTERNAL const char*
_netplan_netdef_id(NetplanNetDefinition* netdef)
{
return netdef->id;
}
NETPLAN_ABI const char *
netplan_netdef_get_filename(const NetplanNetDefinition* netdef)
{
g_assert(netdef);
return netdef->filepath;
}
/*
* Deprecated, use _netplan_netdef_get_embedded_switch_mode instead
*/
NETPLAN_DEPRECATED NETPLAN_INTERNAL const char*
netplan_netdef_get_embedded_switch_mode(const NetplanNetDefinition* netdef)
{
g_assert(netdef);
return netdef->embedded_switch_mode;
}
/**
* Extract the netplan netdef ID from a NetworkManager connection profile (keyfile),
* generated by netplan. Used by the NetworkManager YAML backend.
*
* Note: Use netplan_get_id_from_nm_filepath instead.
*/
gchar*
netplan_get_id_from_nm_filename(const char* filename, const char* ssid)
{
size_t out_buf_size = 0;
ssize_t ret = 0;
g_autofree char* out_buffer = NULL;
out_buf_size = strlen(filename);
out_buffer = calloc(out_buf_size, 1);
ret = netplan_get_id_from_nm_filepath(filename, ssid, out_buffer, out_buf_size);
if (ret <= 0)
return NULL;
return g_strndup(out_buffer, ret);
}
// LCOV_EXCL_STOP
netplan-0.107.1/src/dbus.c 0000664 0000000 0000000 00000076361 14536110317 0015227 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "_features.h"
#include "util-internal.h"
typedef struct {
sd_bus_slot *slot;
gboolean invalidated;
} NetplanConfigData;
typedef struct {
sd_bus *bus;
sd_event_source *try_es;
GPid try_pid; /* semaphore. There can only be one 'netplan try' child process at a time */
const char *config_id; /* current config ID, during any io.netplan.Netplan.Config calls */
char *handler_id; /* copy of pending config ID, during io.netplan.Netplan.Config.Try() */
char *config_dirty; /* Currently pending Set() config object id */
GHashTable *config_data; /* data of to the /io/netplan/Netplan/config/ objects */
} NetplanData;
static const char* NETPLAN_SUBDIRS[3] = {"etc", "run", "lib"};
static const char* NETPLAN_GLOBAL_CONFIG = "BACKUP";
static char* NETPLAN_ROOT = "/"; /* Can be modified for testing netplan-dbus */
static void
invalidate_other_config(gpointer key, gpointer value, gpointer user_data)
{
const char *id = key;
const char *current_config_id = user_data;
NetplanConfigData *cd = value;
if (current_config_id == NULL)
cd->invalidated = FALSE;
else if (g_strcmp0(id, current_config_id))
cd->invalidated = TRUE;
}
static int
terminate_try_child_process(int status, NetplanData *d, const char *config_id)
{
sd_bus_message *msg = NULL;
g_autofree gchar *path = NULL;
int r = 0;
if (!WIFEXITED(status))
fprintf(stderr, "'netplan try' exited with status: %d\n", WEXITSTATUS(status)); // LCOV_EXCL_LINE
/* Cleanup current 'netplan try' child process */
sd_event_source_unref(d->try_es);
d->try_es = NULL;
g_spawn_close_pid (d->try_pid);
d->try_pid = -1; /* unlock semaphore */
/* Send .Changed() signal on DBus */
if (config_id) {
path = g_strdup_printf("/io/netplan/Netplan/config/%s", config_id);
r = sd_bus_message_new_signal(d->bus, &msg, path,
"io.netplan.Netplan.Config", "Changed");
}
if (r < 0) {
// LCOV_EXCL_START
fprintf(stderr, "Could not create .Changed() signal: %s\n", strerror(-r));
return r;
// LCOV_EXCL_STOP
}
r = sd_bus_send(d->bus, msg, NULL);
if (r < 0)
fprintf(stderr, "Could not send .Changed() signal: %s\n", strerror(-r)); // LCOV_EXCL_LINE
sd_bus_message_unrefp(&msg);
return r;
}
static int
_try_accept(bool accept, sd_bus_message *m, NetplanData *d, sd_bus_error *ret_error)
{
g_autoptr(GError) error = NULL;
int status = -1;
int signal = SIGUSR1;
if (!accept) signal = SIGINT;
/* Do not send the accept/reject signal, if this call is for another config state */
if (d->handler_id != NULL && g_strcmp0(d->config_id, d->handler_id))
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED, "Another 'netplan try' process is already running");
/* ATTENTION: There might be a race here:
* When this accept/reject method is called at the same time as the 'netplan try'
* python process is reverting and closing itself. Not sure what to do about it...
* Maybe this needs to be fixed in python code, so that the
* 'netplan.terminal.InputRejected' exception (i.e. self-revert) cannot be
* interrupted by another exception/signal */
/* Send confirm (SIGUSR1) or cancel (SIGINT) signal to 'netplan try' process.
* Wait for the child process to stop, synchronously.
* Check return code/errors. */
kill(d->try_pid, signal);
waitpid(d->try_pid, &status, 0);
#if GLIB_CHECK_VERSION (2, 70, 0)
g_spawn_check_wait_status(status, &error);
#else
g_spawn_check_exit_status(status, &error);
#endif
if (error != NULL)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED, "netplan try failed: %s", error->message); // LCOV_EXCL_LINE
terminate_try_child_process(status, d, d->config_id);
return sd_bus_reply_method_return(m, "b", true);
}
static int
_copy_yaml_state(char *src_root, char *dst_root, sd_bus_error *ret_error)
{
glob_t gl;
g_autoptr(GError) err = NULL;
int r = find_yaml_glob(src_root, &gl);
if (!!r)
// LCOV_EXCL_START
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"Failed glob for YAML files\n");
// LCOV_EXCL_STOP
/* Copy all *.yaml files from "/SRC_ROOT/{etc,run,lib}/netplan/" to
* "/DST_ROOT/{etc,run,lib}/netplan/" */
GFile *source = NULL;
GFile *dest = NULL;
gchar *dest_path = NULL;
size_t len = strlen(src_root);
for (size_t i = 0; i < gl.gl_pathc; ++i) {
dest_path = g_build_path(G_DIR_SEPARATOR_S, dst_root, (gl.gl_pathv[i])+len, NULL);
source = g_file_new_for_path(gl.gl_pathv[i]);
dest = g_file_new_for_path(dest_path);
g_file_copy(source, dest, G_FILE_COPY_OVERWRITE
|G_FILE_COPY_NOFOLLOW_SYMLINKS
|G_FILE_COPY_ALL_METADATA,
NULL, NULL, NULL, &err);
if (err != NULL) {
// LCOV_EXCL_START
r = sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"Failed to copy file %s -> %s: %s\n",
g_file_get_path(source), g_file_get_path(dest),
err->message);
g_object_unref(source);
g_object_unref(dest);
g_free(dest_path);
globfree(&gl);
return r;
// LCOV_EXCL_STOP
}
g_object_unref(source);
g_object_unref(dest);
g_free(dest_path);
}
globfree(&gl);
return r;
}
static bool
_clear_tmp_state(const char *config_id, NetplanData *d)
{
g_autofree gchar *rootdir = NULL;
/* Remove tmp YAML files */
rootdir = g_strdup_printf("%s/run/netplan/config-%s", NETPLAN_ROOT, config_id);
unlink_glob(rootdir, "/{etc,run,lib}/netplan/*.yaml");
/* Remove tmp state directories */
char *subdir = NULL;
for (int i = 0; i < 3; i++) {
subdir = g_strdup_printf("%s/%s/netplan", rootdir, NETPLAN_SUBDIRS[i]);
rmdir(subdir);
g_free(subdir);
subdir = g_strdup_printf("%s/%s", rootdir, NETPLAN_SUBDIRS[i]);
rmdir(subdir);
g_free(subdir);
}
rmdir(rootdir);
/* No cleanup of DBus object needed, if config_id points to NETPLAN_GLOBAL_CONFIG (backup) */
if (config_id != NETPLAN_GLOBAL_CONFIG) {
/* Clear config object from DBus, by unref the appropriate slot */
NetplanConfigData *cd = g_hash_table_lookup(d->config_data, config_id);
sd_bus_slot_unref(cd->slot); /* Clear value/slot */
g_free(cd); /* Clear value/struct */
g_hash_table_remove(d->config_data, config_id); /* Clear key */
d->config_dirty = NULL;
/* TODO: HashTable error handling */
}
return TRUE;
}
static int
_backup_global_state(sd_bus_error *ret_error)
{
int r = 0;
g_autofree gchar *path = NULL;
path = g_strdup_printf("%s/run/netplan/config-%s", NETPLAN_ROOT, NETPLAN_GLOBAL_CONFIG);
/* Create {etc,run,lib} subdirs with owner r/w permissions */
char *subdir = NULL;
for (int i = 0; i < 3; i++) {
subdir = g_strdup_printf("%s/%s/netplan", path, NETPLAN_SUBDIRS[i]);
r = g_mkdir_with_parents(subdir, 0700);
if (r < 0)
// LCOV_EXCL_START
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"Failed to create '%s': %s\n", subdir, strerror(errno));
// LCOV_EXCL_STOP
g_free(subdir);
}
/* Copy main *.yaml files from /{etc,run,lib}/netplan/ to GLOBAL backup dir */
r = _copy_yaml_state(NETPLAN_ROOT, path, ret_error);
return r;
}
/**
* io.netplan.Netplan methods
*/
static int
method_apply(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
g_autoptr(GError) err = NULL;
g_autofree gchar *stdout = NULL;
g_autofree gchar *stderr = NULL;
g_autofree gchar *state = NULL;
gint exit_status = 0;
NetplanData *d = userdata;
/* Accept the current 'netplan try', if active.
* Otherwise execute 'netplan apply' directly. */
if (d->try_pid > 0)
return _try_accept(TRUE, m, userdata, ret_error);
if (d->config_id)
state = g_strdup_printf("--state=%s/run/netplan/config-%s", NETPLAN_ROOT, NETPLAN_GLOBAL_CONFIG);
gchar *argv[] = {SBINDIR "/" "netplan", "apply", state, NULL};
// for tests only: allow changing what netplan to run
if (getenv("DBUS_TEST_NETPLAN_CMD") != 0)
argv[0] = getenv("DBUS_TEST_NETPLAN_CMD");
g_spawn_sync("/", argv, NULL, 0, NULL, NULL, &stdout, &stderr, &exit_status, &err);
// LCOV_EXCL_START
if (err != NULL)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"cannot run netplan apply: %s", err->message);
#if GLIB_CHECK_VERSION (2, 70, 0)
g_spawn_check_wait_status(exit_status, &err);
#else
g_spawn_check_exit_status(exit_status, &err);
#endif
if (err != NULL)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"netplan apply failed: %s\nstdout: '%s'\nstderr: '%s'",
err->message, stdout, stderr);
// LCOV_EXCL_STOP
return sd_bus_reply_method_return(m, "b", true);
}
static int
method_generate(sd_bus_message *m, __unused void *userdata, sd_bus_error *ret_error)
{
g_autoptr(GError) err = NULL;
g_autofree gchar *stdout = NULL;
g_autofree gchar *stderr = NULL;
gint exit_status = 0;
gchar *argv[] = {SBINDIR "/" "netplan", "generate", NULL};
// for tests only: allow changing what netplan to run
if (getenv("DBUS_TEST_NETPLAN_CMD") != 0)
argv[0] = getenv("DBUS_TEST_NETPLAN_CMD");
g_spawn_sync("/", argv, NULL, 0, NULL, NULL, &stdout, &stderr, &exit_status, &err);
// LCOV_EXCL_START
if (err != NULL)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"cannot run netplan generate: %s", err->message);
#if GLIB_CHECK_VERSION (2, 70, 0)
g_spawn_check_wait_status(exit_status, &err);
#else
g_spawn_check_exit_status(exit_status, &err);
#endif
if (err != NULL)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"netplan generate failed: %s\nstdout: '%s'\nstderr: '%s'",
err->message, stdout, stderr);
// LCOV_EXCL_STOP
return sd_bus_reply_method_return(m, "b", true);
}
static int
method_info(sd_bus_message *m, __unused void *userdata, __unused sd_bus_error *ret_error)
{
sd_bus_message *reply = NULL;
gint exit_status = 0;
exit_status = sd_bus_message_new_method_return(m, &reply);
if (exit_status < 0)
return exit_status; // LCOV_EXCL_LINE
exit_status = sd_bus_message_open_container(reply, 'a', "(sv)");
if (exit_status < 0)
return exit_status; // LCOV_EXCL_LINE
exit_status = sd_bus_message_open_container(reply, 'r', "sv");
if (exit_status < 0)
return exit_status; // LCOV_EXCL_LINE
exit_status = sd_bus_message_append(reply, "s", "Features");
if (exit_status < 0)
return exit_status; // LCOV_EXCL_LINE
exit_status = sd_bus_message_open_container(reply, 'v', "as");
if (exit_status < 0)
return exit_status; // LCOV_EXCL_LINE
exit_status = sd_bus_message_append_strv(reply, (char**)feature_flags);
if (exit_status < 0)
return exit_status; // LCOV_EXCL_LINE
exit_status = sd_bus_message_close_container(reply);
if (exit_status < 0)
return exit_status; // LCOV_EXCL_LINE
exit_status = sd_bus_message_close_container(reply);
if (exit_status < 0)
return exit_status; // LCOV_EXCL_LINE
exit_status = sd_bus_message_close_container(reply);
if (exit_status < 0)
return exit_status; // LCOV_EXCL_LINE
return sd_bus_send(NULL, reply, NULL);
}
static int
method_get(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
NetplanData *d = userdata;
g_autoptr(GError) err = NULL;
g_autofree gchar *stdout = NULL;
g_autofree gchar *stderr = NULL;
g_autofree gchar *root_dir = NULL;
gint exit_status = 0;
if (d->config_id)
root_dir = g_strdup_printf("--root-dir=%s/run/netplan/config-%s", NETPLAN_ROOT, d->config_id);
gchar *argv[] = {SBINDIR "/" "netplan", "get", "all", root_dir, NULL};
// for tests only: allow changing what netplan to run
if (getenv("DBUS_TEST_NETPLAN_CMD") != 0)
argv[0] = getenv("DBUS_TEST_NETPLAN_CMD");
g_spawn_sync("/", argv, NULL, 0, NULL, NULL, &stdout, &stderr, &exit_status, &err);
if (err != NULL)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED, "cannot run netplan get: %s", err->message); // LCOV_EXCL_LINE
#if GLIB_CHECK_VERSION (2, 70, 0)
g_spawn_check_wait_status(exit_status, &err);
#else
g_spawn_check_exit_status(exit_status, &err);
#endif
if (err != NULL)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED, "netplan get failed: %s\nstdout: '%s'\nstderr: '%s'", err->message, stdout, stderr); // LCOV_EXCL_LINE
return sd_bus_reply_method_return(m, "s", stdout);
}
static int
method_set(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
NetplanData *d = userdata;
g_autoptr(GError) err = NULL;
g_autofree gchar *stdout = NULL;
g_autofree gchar *stderr = NULL;
g_autofree gchar *origin = NULL;
g_autofree gchar *root_dir = NULL;
gint exit_status = 0;
char *args[2] = {NULL, NULL};
char *config_delta = NULL;
char *origin_hint = NULL;
guint cur_arg = 0;
if (sd_bus_message_read(m, "ss", &config_delta, &origin_hint) < 0)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED, "cannot extract config_delta or origin_hint"); // LCOV_EXCL_LINE
if (!!strcmp(origin_hint, "")) {
origin = g_strdup_printf("--origin-hint=%s", origin_hint);
args[cur_arg] = origin;
cur_arg++;
}
if (d->config_id) {
root_dir = g_strdup_printf("--root-dir=%s/run/netplan/config-%s", NETPLAN_ROOT, d->config_id);
args[cur_arg] = root_dir;
cur_arg++;
}
gchar *argv[] = {SBINDIR "/" "netplan", "set", config_delta, args[0], args[1], NULL};
// for tests only: allow changing what netplan to run
if (getenv("DBUS_TEST_NETPLAN_CMD") != 0)
argv[0] = getenv("DBUS_TEST_NETPLAN_CMD");
g_spawn_sync("/", argv, NULL, 0, NULL, NULL, &stdout, &stderr, &exit_status, &err);
if (err != NULL)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED, "cannot run netplan set %s: %s", config_delta, err->message); // LCOV_EXCL_LINE
#if GLIB_CHECK_VERSION (2, 70, 0)
g_spawn_check_wait_status(exit_status, &err);
#else
g_spawn_check_exit_status(exit_status, &err);
#endif
if (err != NULL)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED, "netplan set failed: %s\nstdout: '%s'\nstderr: '%s'", err->message, stdout, stderr); // LCOV_EXCL_LINE
return sd_bus_reply_method_return(m, "b", true);
}
static int
netplan_try_cancelled_cb(__unused sd_event_source *es, const siginfo_t *si, void* userdata)
{
NetplanData *d = userdata;
g_autofree gchar *state_dir = NULL;
int r = 0;
if (d->handler_id) {
/* Delete GLOBAL state */
unlink_glob(NETPLAN_ROOT, "/{etc,run,lib}/netplan/*.yaml");
/* Restore GLOBAL backup config state to main rootdir */
state_dir = g_strdup_printf("%s/run/netplan/config-%s", NETPLAN_ROOT, NETPLAN_GLOBAL_CONFIG);
r = _copy_yaml_state(state_dir, NETPLAN_ROOT, NULL);
if (r < 0) return r;
/* Un-invalidate all other current config objects */
if (!g_strcmp0(d->handler_id, d->config_dirty))
g_hash_table_foreach(d->config_data, invalidate_other_config, NULL);
/* Clear GLOBAL backup and config state */
_clear_tmp_state(NETPLAN_GLOBAL_CONFIG, d);
_clear_tmp_state(d->handler_id, d);
}
r = terminate_try_child_process(si->si_status, d, d->handler_id);
/* free and reset handler_id, i.e. copy of config state ID */
g_free(d->handler_id);
d->handler_id = NULL; /* unlock pending config ID */
return r;
}
static int
method_try(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
g_autoptr(GError) err = NULL;
g_autofree gchar *timeout = NULL;
g_autofree gchar *state = NULL;
g_autofree gchar *netplan_try_stamp = NULL;
struct stat buf;
gint child_stdin = -1; /* child process needs an input to function correctly */
guint seconds = 0;
int r = -1;
NetplanData *d = userdata;
if (sd_bus_message_read_basic (m, 'u', &seconds) < 0)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED, "cannot extract timeout_seconds"); // LCOV_EXCL_LINE
if (seconds > 0)
timeout = g_strdup_printf("--timeout=%u", seconds);
if (d->config_id)
state = g_strdup_printf("--state=%s/run/netplan/config-%s", NETPLAN_ROOT, NETPLAN_GLOBAL_CONFIG);
gchar *argv[] = {SBINDIR "/" "netplan", "try", timeout, state, NULL};
// for tests only: allow changing what netplan to run
if (getenv("DBUS_TEST_NETPLAN_CMD") != 0)
argv[0] = getenv("DBUS_TEST_NETPLAN_CMD");
/* Delete any left-over netplan-try.ready stamp file, if it exists */
netplan_try_stamp = g_build_path("/", NETPLAN_ROOT, "run", "netplan", "netplan-try.ready", NULL);
unlink(netplan_try_stamp);
/* Launch 'netplan try' child process, lock 'try_pid' to real PID */
g_spawn_async_with_pipes("/", argv, NULL,
G_SPAWN_DO_NOT_REAP_CHILD|G_SPAWN_STDOUT_TO_DEV_NULL,
NULL, NULL, &d->try_pid, &child_stdin, NULL, NULL, &err);
if (err)
// LCOV_EXCL_START
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"cannot run netplan try: %s", err->message);
// LCOV_EXCL_STOP
/* Register an event handler, trigged when the child process exits */
if (d->config_id)
d->handler_id = g_strdup(d->config_id); /* to free in event handler */
r = sd_event_add_child(sd_bus_get_event(d->bus), &d->try_es, d->try_pid,
WEXITED, netplan_try_cancelled_cb, d);
if (r < 0)
// LCOV_EXCL_START
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"cannot watch 'netplan try' child: %s", strerror(-r));
// LCOV_EXCL_STOP
/* wait for the /run/netplan/netplan-try.ready stamp file to appear */
guint poll_timeout = 1000;
/* Replace the default timeout with the one specified by the caller */
if (seconds > 0)
poll_timeout = seconds * 100;
/* Timeout after up to 10 sec of waiting for the stamp file */
for (guint i = 0; i < poll_timeout; i++) {
struct timespec timeout = {
.tv_sec = 0,
.tv_nsec = 1000 * 1000 * 10, // 10 ms
};
if (stat(netplan_try_stamp, &buf) == 0)
break;
nanosleep(&timeout, NULL);
}
if (stat(netplan_try_stamp, &buf) != 0) {
g_debug("cannot find %s stamp file", netplan_try_stamp);
return sd_bus_reply_method_return(m, "b", false);
}
return sd_bus_reply_method_return(m, "b", true);
}
/**
* io.netplan.Netplan.Config methods
*/
/* netplan-feature: dbus-config */
static int
method_config_apply(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
NetplanData *d = userdata;
g_autofree gchar *state_dir = NULL;
int r = 0;
/* trim 27 chars (i.e. "/io/netplan/Netplan/config/") from path to get the config ID */
d->config_id = sd_bus_message_get_path(m) + 27;
NetplanConfigData *cd = g_hash_table_lookup(d->config_data, d->config_id);
if (cd->invalidated)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"This config was invalidated by another config object\n");
/* Invalidate all other current config objects */
g_hash_table_foreach(d->config_data, invalidate_other_config, (void*)d->config_id);
d->config_dirty = g_strdup(d->config_id);
if (d->try_pid < 0) {
r = _backup_global_state(ret_error);
if (r < 0)
return r; // LCOV_EXCL_LINE
/* Delete GLOBAL state */
unlink_glob(NETPLAN_ROOT, "/{etc,run,lib}/netplan/*.yaml");
/* Copy current config state to GLOBAL */
state_dir = g_strdup_printf("%s/run/netplan/config-%s", NETPLAN_ROOT, d->config_id);
r = _copy_yaml_state(state_dir, NETPLAN_ROOT, ret_error);
if (r < 0) return r;
d->handler_id = g_strdup(d->config_id);
}
r = method_apply(m, d, ret_error);
/* Clear GLOBAL backup and config state */
_clear_tmp_state(NETPLAN_GLOBAL_CONFIG, d);
_clear_tmp_state(d->config_id, d);
/* unlock current config ID and handler ID */
d->config_id = NULL;
g_free(d->handler_id);
d->handler_id = NULL;
return r;
}
static int
method_config_get(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
NetplanData *d = userdata;
/* trim 27 chars (i.e. "/io/netplan/Netplan/config/") from path to get the config ID */
d->config_id = sd_bus_message_get_path(m) + 27;
int r = method_get(m, userdata, ret_error);
/* Reset config_id for next method call */
d->config_id = NULL;
return r;
}
static int
method_config_set(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
NetplanData *d = userdata;
/* trim 27 chars (i.e. "/io/netplan/Netplan/config/") from path to get the config ID */
d->config_id = sd_bus_message_get_path(m) + 27;
NetplanConfigData *cd = g_hash_table_lookup(d->config_data, d->config_id);
if (cd->invalidated)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"This config was invalidated by another config object\n");
int r = method_set(m, d, ret_error);
/* Invalidate all other current config objects */
g_hash_table_foreach(d->config_data, invalidate_other_config, (void*)d->config_id);
d->config_dirty = g_strdup(d->config_id);
/* Reset config_id for next method call */
d->config_id = NULL;
return r;
}
static int
method_config_try(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
NetplanData *d = userdata;
g_autofree gchar *state_dir = NULL;
const char *config_id = sd_bus_message_get_path(m) + 27;
if (d->try_pid > 0)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"Another Try() is currently in progress: PID %d\n", d->try_pid);
NetplanConfigData *cd = g_hash_table_lookup(d->config_data, config_id);
if (cd->invalidated)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"This config was invalidated by another config object\n");
int r = 0;
/* Lock current child process temporarily until we have a real PID */
d->try_pid = G_MAXINT;
d->config_id = config_id;
r = _backup_global_state(ret_error);
if (r < 0)
return r; // LCOV_EXCL_LINE
/* Clear main *.yaml files */
unlink_glob(NETPLAN_ROOT, "/{etc,run,lib}/netplan/*.yaml");
/* Copy current config *.yaml state to main rootdir (i.e. /etc/netplan/) */
state_dir = g_strdup_printf("%s/run/netplan/config-%s", NETPLAN_ROOT, d->config_id);
r = _copy_yaml_state(state_dir, NETPLAN_ROOT, ret_error);
if (r < 0) return r;
/* Exec try */
r = method_try(m, userdata, ret_error);
d->config_id = NULL;
return r;
}
static int
method_config_cancel(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
NetplanData *d = userdata;
g_autofree gchar *state_dir = NULL;
int r = 0;
/* trim 27 chars (i.e. "/io/netplan/Netplan/config/") from path to get the config ID */
d->config_id = sd_bus_message_get_path(m) + 27;
if (!g_strcmp0(d->config_id, d->config_dirty))
/* Un-invalidate all other current config objects */
g_hash_table_foreach(d->config_data, invalidate_other_config, NULL);
/* Cancel the current 'netplan try' process */
if (d->try_pid > 0)
r = _try_accept(FALSE, m, d, ret_error);
else
r = sd_bus_reply_method_return(m, "b", true);
if (d->handler_id && !g_strcmp0(d->config_id, d->handler_id)) {
/* Delete GLOBAL state */
unlink_glob(NETPLAN_ROOT, "/{etc,run,lib}/netplan/*.yaml");
/* Restore GLOBAL backup config state to main rootdir */
state_dir = g_strdup_printf("%s/run/netplan/config-%s", NETPLAN_ROOT, NETPLAN_GLOBAL_CONFIG);
r = _copy_yaml_state(state_dir, NETPLAN_ROOT, ret_error);
if (r < 0) return r;
/* Clear GLOBAL backup and config state */
_clear_tmp_state(NETPLAN_GLOBAL_CONFIG, d);
/* Clear pending Try() handler ID */
g_free(d->handler_id);
d->handler_id = NULL;
}
/* Clear tmp state */
_clear_tmp_state(d->config_id, d);
d->config_id = NULL;
return r;
}
static const sd_bus_vtable config_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("Apply", "", "b", method_config_apply, 0),
SD_BUS_METHOD("Get", "", "s", method_config_get, 0),
SD_BUS_METHOD("Set", "ss", "b", method_config_set, 0),
SD_BUS_METHOD("Try", "u", "b", method_config_try, 0),
SD_BUS_METHOD("Cancel", "", "b", method_config_cancel, 0),
SD_BUS_VTABLE_END
};
/**
* Link between io.netplan.Netplan and io.netplan.Netplan.Config
*/
static int
method_config(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
NetplanData *d = userdata;
sd_bus_slot *slot = NULL;
g_autoptr(GError) err = NULL;
g_autofree gchar *tmpl = NULL;
g_autofree gchar *dir = NULL;
gchar *path = NULL;
int r = 0;
/* Create state directory, according to "run/netplan/config-XXXXXX" template */
tmpl = g_build_path("/", NETPLAN_ROOT, "run", "netplan", "config-XXXXXX", NULL);
dir = g_path_get_dirname(tmpl);
r = g_mkdir_with_parents(dir, 0700);
path = g_mkdtemp(tmpl); // returns pointer to tmpl (with modified string)
if (r < 0 || !path)
// LCOV_EXCL_START
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"Failed to create temp dir: %s\n", strerror(errno));
// LCOV_EXCL_STOP
/* Extract the last 6 randomly generated chars (i.e. "XXXXXX" from template) */
const char *id = path + strlen(path) - 6;
const char *obj_path = g_strdup_printf("/io/netplan/Netplan/config/%s", id);
r = sd_bus_add_object_vtable(d->bus, &slot, obj_path,
"io.netplan.Netplan.Config", config_vtable, d);
// LCOV_EXCL_START
if (r < 0)
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"Failed to add 'config' object: %s\n", strerror(-r));
NetplanConfigData *cd = g_new0(NetplanConfigData, 1);
cd->slot = slot;
/* Cannot Set()/Apply() if another Set() is currently pending */
cd->invalidated = d->config_dirty ? TRUE : FALSE;
if (!g_hash_table_insert(d->config_data, g_strdup(id), cd))
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"Failed to add object data to HashTable\n");
// LCOV_EXCL_STOP
/* Create {etc,run,lib} subdirs with owner r/w permissions */
char *subdir = NULL;
for (int i = 0; i < 3; i++) {
subdir = g_strdup_printf("%s/%s/netplan", path, NETPLAN_SUBDIRS[i]);
r = g_mkdir_with_parents(subdir, 0700);
if (r < 0)
// LCOV_EXCL_START
return sd_bus_error_setf(ret_error, SD_BUS_ERROR_FAILED,
"Failed to create '%s': %s\n", subdir, strerror(errno));
// LCOV_EXCL_STOP
g_free(subdir);
}
/* Copy all *.yaml files from /{etc,run,lib}/netplan/ to temp dir */
r = _copy_yaml_state(NETPLAN_ROOT, path, ret_error);
if (r < 0) return r;
return sd_bus_reply_method_return(m, "o", obj_path);
}
static const sd_bus_vtable netplan_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("Apply", "", "b", method_apply, 0),
SD_BUS_METHOD("Generate", "", "b", method_generate, 0),
SD_BUS_METHOD("Info", "", "a(sv)", method_info, 0),
SD_BUS_METHOD("Config", "", "o", method_config, 0),
SD_BUS_VTABLE_END
};
/**
* DBus setup
*/
static int
terminate_mainloop_cb(__unused sd_event_source *es, __unused const struct signalfd_siginfo *si, void* userdata) {
sd_event *event = userdata;
/* Gracefully terminate the mainloop, to write GCOV output */
sd_event_exit(event, 0);
return 0;
}
int
main(__unused int argc, __unused char *argv[])
{
sd_bus_slot *slot = NULL;
sd_bus *bus = NULL;
sd_event *event = NULL;
NetplanData *data = g_new0(NetplanData, 1);
sigset_t mask;
int r;
// for tests only: allow changing which rootdir to use to copy files around
if (getenv("DBUS_TEST_NETPLAN_ROOT") != 0)
NETPLAN_ROOT = getenv("DBUS_TEST_NETPLAN_ROOT");
/* TODO: consider sd_bus_default(&bus) for easier testing on session/user bus */
r = sd_bus_open_system(&bus);
if (r < 0) {
// LCOV_EXCL_START
fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r));
goto finish;
// LCOV_EXCL_STOP
}
r = sd_event_new(&event);
if (r < 0) {
// LCOV_EXCL_START
fprintf(stderr, "Failed to create event loop: %s\n", strerror(-r));
goto finish;
// LCOV_EXCL_STOP
}
/* Initialize the userdata */
data->bus = bus;
data->try_pid = -1;
data->config_id = NULL;
data->handler_id = NULL;
data->config_dirty = NULL;
/* TODO: define a proper free/cleanup function for sd_bus_slot_unref() */
data->config_data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
r = sd_bus_add_object_vtable(bus, &slot,
"/io/netplan/Netplan", /* object path */
"io.netplan.Netplan", /* interface name */
netplan_vtable,
data);
if (r < 0) {
// LCOV_EXCL_START
fprintf(stderr, "Failed to issue method call: %s\n", strerror(-r));
goto finish;
// LCOV_EXCL_STOP
}
r = sd_bus_request_name(bus, "io.netplan.Netplan", 0);
if (r < 0) {
fprintf(stderr, "Failed to acquire service name: %s\n", strerror(-r));
goto finish;
}
r = sd_bus_attach_event(bus, event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0) {
// LCOV_EXCL_START
fprintf(stderr, "Failed to attach event loop: %s\n", strerror(-r));
goto finish;
// LCOV_EXCL_STOP
}
/* Mask the SIGCHLD signal, so we can listen to it via mainloop */
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);
/* Start the event loop, wait for requests */
sd_event_add_signal(event, NULL, SIGTERM, terminate_mainloop_cb, event);
r = sd_event_loop(event);
if (r < 0)
fprintf(stderr, "Failed mainloop: %s\n", strerror(-r)); // LCOV_EXCL_LINE
finish:
g_free(data);
sd_event_unref(event);
sd_bus_slot_unref(slot);
sd_bus_unref(bus);
/* TODO: unref all slots from HashTable */
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
netplan-0.107.1/src/error.c 0000664 0000000 0000000 00000014456 14536110317 0015420 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2019 Canonical, Ltd.
* Author: Mathieu Trudel-Lapierre
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#include
#include
#include
#include
#include
#include "util.h"
#include "parse.h"
#include "types-internal.h"
#include "util-internal.h"
/****************************************************
* Loading and error handling
****************************************************/
static void
write_error_marker(GString *message, int column)
{
int i;
for (i = 0; (column > 0 && i < column); i++)
g_string_append_printf(message, " ");
g_string_append_printf(message, "^");
}
static char *
get_syntax_error_context(const NetplanParser* npp, const int line_num, const int column, GError **error)
{
GString *message = NULL;
GFile *cur_file = g_file_new_for_path(npp->current.filepath);
GFileInputStream *file_stream;
GDataInputStream *stream;
gsize len;
gchar* line = NULL;
message = g_string_sized_new(200);
file_stream = g_file_read(cur_file, NULL, error);
stream = g_data_input_stream_new (G_INPUT_STREAM(file_stream));
g_object_unref(file_stream);
for (int i = 0; i < line_num + 1; i++) {
g_free(line);
line = g_data_input_stream_read_line(stream, &len, NULL, error);
}
g_string_append_printf(message, "%s\n", line);
g_free(line);
write_error_marker(message, column);
g_object_unref(stream);
g_object_unref(cur_file);
return g_string_free(message, FALSE);
}
static char *
get_parser_error_context(const yaml_parser_t *parser, __unused GError **error)
{
GString *message = NULL;
unsigned char* line = parser->buffer.pointer;
unsigned char* current = line;
message = g_string_sized_new(200);
while (current > parser->buffer.start) {
current--;
if (*current == '\n') {
line = current + 1;
break;
}
}
if (current <= parser->buffer.start)
line = parser->buffer.start;
current = line + 1;
while (current <= parser->buffer.last) {
if (*current == '\n') {
*current = '\0';
break;
}
current++;
}
g_string_append_printf(message, "%s\n", line);
write_error_marker(message, parser->problem_mark.column);
return g_string_free(message, FALSE);
}
gboolean
parser_error(const yaml_parser_t* parser, const char* yaml, GError** error)
{
char *error_context = get_parser_error_context(parser, error);
yaml = yaml ? yaml : "(unnamed file)";
if ((char)*parser->buffer.pointer == '\t')
g_set_error(error, NETPLAN_PARSER_ERROR, NETPLAN_ERROR_INVALID_YAML,
"%s:%zu:%zu: Invalid YAML: tabs are not allowed for indent:\n%s",
yaml,
parser->problem_mark.line + 1,
parser->problem_mark.column + 1,
error_context);
else if (((char)*parser->buffer.pointer == ' ' || (char)*parser->buffer.pointer == '\0')
&& !parser->token_available)
g_set_error(error, NETPLAN_PARSER_ERROR, NETPLAN_ERROR_INVALID_YAML,
"%s:%zu:%zu: Invalid YAML: aliases are not supported:\n%s",
yaml,
parser->problem_mark.line + 1,
parser->problem_mark.column + 1,
error_context);
else if (parser->state == YAML_PARSE_BLOCK_MAPPING_KEY_STATE)
g_set_error(error, NETPLAN_PARSER_ERROR, NETPLAN_ERROR_INVALID_YAML,
"%s:%zu:%zu: Invalid YAML: inconsistent indentation:\n%s",
yaml,
parser->problem_mark.line + 1,
parser->problem_mark.column + 1,
error_context);
else {
g_set_error(error, NETPLAN_PARSER_ERROR, NETPLAN_ERROR_INVALID_YAML,
"%s:%zu:%zu: Invalid YAML: %s:\n%s",
yaml,
parser->problem_mark.line + 1,
parser->problem_mark.column + 1,
parser->problem,
error_context);
}
g_free(error_context);
return FALSE;
}
/**
* Put a YAML specific error message for @node into @error.
*/
gboolean
yaml_error(const NetplanParser *npp, const yaml_node_t* node, GError** error, const char* msg, ...)
{
va_list argp;
char* s;
char* error_context = NULL;
va_start(argp, msg);
g_vasprintf(&s, msg, argp);
if (node != NULL && npp->current.filepath != NULL) {
error_context = get_syntax_error_context(npp, node->start_mark.line, node->start_mark.column, error);
g_set_error(error, NETPLAN_PARSER_ERROR, NETPLAN_ERROR_INVALID_CONFIG,
"%s:%zu:%zu: Error in network definition: %s\n%s",
npp->current.filepath,
node->start_mark.line + 1,
node->start_mark.column + 1,
s,
error_context);
} else if (npp->current.filepath) {
g_set_error(error, NETPLAN_VALIDATION_ERROR, NETPLAN_ERROR_CONFIG_VALIDATION,
"%s: Error in network definition: %s", npp->current.filepath, s);
} else {
g_set_error(error, NETPLAN_VALIDATION_ERROR, NETPLAN_ERROR_CONFIG_GENERIC,
"Error in network definition: %s", s);
}
g_free(s);
va_end(argp);
g_free(error_context);
return FALSE;
}
void
netplan_error_clear(NetplanError** error)
{
g_clear_error(error);
}
ssize_t
netplan_error_message(NetplanError* error, char* buf, size_t buf_size)
{
return netplan_copy_string(error->message, buf, buf_size);
}
uint64_t
netplan_error_code(NetplanError* error) {
uint64_t error_code = (uint64_t)error->domain << 32 | (uint64_t)error->code;
return error_code;
}
netplan-0.107.1/src/error.h 0000664 0000000 0000000 00000001775 14536110317 0015425 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2019 Canonical, Ltd.
* Author: Mathieu Trudel-Lapierre
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#pragma once
#include
#include
#include
#include
#include "parse.h"
gboolean
parser_error(const yaml_parser_t* parser, const char* yaml, GError** error);
gboolean
yaml_error(const NetplanParser *npp, const yaml_node_t* node, GError** error, const char* msg, ...);
netplan-0.107.1/src/generate.c 0000664 0000000 0000000 00000032357 14536110317 0016061 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2016 Canonical, Ltd.
* Author: Martin Pitt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "util.h"
#include "util-internal.h"
#include "parse.h"
#include "names.h"
#include "networkd.h"
#include "nm.h"
#include "openvswitch.h"
#include "sriov.h"
#include "netplan.h"
static gchar* rootdir;
static gchar** files;
static gboolean any_networkd = FALSE;
static gboolean any_nm = FALSE;
static gchar* mapping_iface;
static GOptionEntry options[] = {
{"root-dir", 'r', 0, G_OPTION_ARG_FILENAME, &rootdir, "Search for and generate configuration files in this root directory instead of /", NULL},
{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &files, "Read configuration from this/these file(s) instead of /etc/netplan/*.yaml", "[config file ..]"},
{"mapping", 0, 0, G_OPTION_ARG_STRING, &mapping_iface, "Only show the device to backend mapping for the specified interface.", NULL},
{NULL}
};
static void
reload_udevd(void)
{
const gchar *argv[] = { "/bin/udevadm", "control", "--reload", NULL };
g_spawn_sync(NULL, (gchar**)argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, NULL, NULL);
};
/**
* Create enablement symlink for systemd-networkd.service.
*/
static void
enable_networkd(const char* generator_dir)
{
g_autofree char* link = g_build_path(G_DIR_SEPARATOR_S, generator_dir, "multi-user.target.wants", "systemd-networkd.service", NULL);
g_debug("We created networkd configuration, adding %s enablement symlink", link);
safe_mkdir_p_dir(link);
if (symlink("../systemd-networkd.service", link) < 0 && errno != EEXIST) {
// LCOV_EXCL_START
g_fprintf(stderr, "failed to create enablement symlink: %m\n");
exit(1);
// LCOV_EXCL_STOP
}
g_autofree char* link2 = g_build_path(G_DIR_SEPARATOR_S, generator_dir, "network-online.target.wants", "systemd-networkd-wait-online.service", NULL);
safe_mkdir_p_dir(link2);
if (symlink("/lib/systemd/system/systemd-networkd-wait-online.service", link2) < 0 && errno != EEXIST) {
// LCOV_EXCL_START
g_fprintf(stderr, "failed to create enablement symlink: %m\n");
exit(1);
// LCOV_EXCL_STOP
}
}
// LCOV_EXCL_START
/* covered via 'cloud-init' integration test */
static gboolean
check_called_just_in_time()
{
const gchar *argv[] = { "/bin/systemctl", "is-system-running", NULL };
gchar *output = NULL;
g_spawn_sync(NULL, (gchar**)argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, &output, NULL, NULL, NULL);
if (output != NULL && strstr(output, "initializing") != NULL) {
g_free(output);
const gchar *argv2[] = { "/bin/systemctl", "is-active", "network.target", NULL };
gint exit_code = 0;
g_spawn_sync(NULL, (gchar**)argv2, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, &exit_code, NULL);
/* return TRUE, if network.target is not yet active */
#if GLIB_CHECK_VERSION (2, 70, 0)
return !g_spawn_check_wait_status(exit_code, NULL);
#else
return !g_spawn_check_exit_status(exit_code, NULL);
#endif
}
g_free(output);
return FALSE;
};
static void
start_unit_jit(gchar *unit)
{
const gchar *argv[] = { "/bin/systemctl", "start", "--no-block", "--no-ask-password", unit, NULL };
g_spawn_sync(NULL, (gchar**)argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL, NULL, NULL);
};
// LCOV_EXCL_STOP
static int
find_interface(gchar* interface, GHashTable* netdefs)
{
GPtrArray *found;
GFileInfo *info;
GFile *driver_file;
gchar *driver_path;
gchar *driver = NULL;
gpointer key, value;
GHashTableIter iter;
int ret = EXIT_FAILURE;
found = g_ptr_array_new ();
/* Try to get the driver name for the interface... */
driver_path = g_strdup_printf("/sys/class/net/%s/device/driver", interface);
driver_file = g_file_new_for_path (driver_path);
info = g_file_query_info (driver_file,
G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
0, NULL, NULL);
if (info != NULL) {
/* testing for driver matching is done via autopkgtest */
// LCOV_EXCL_START
driver = g_path_get_basename (g_file_info_get_symlink_target (info));
g_object_unref (info);
// LCOV_EXCL_STOP
}
g_object_unref (driver_file);
g_free (driver_path);
g_hash_table_iter_init (&iter, netdefs);
while (g_hash_table_iter_next (&iter, &key, &value)) {
NetplanNetDefinition *nd = (NetplanNetDefinition *) value;
if (!g_strcmp0(nd->set_name, interface))
g_ptr_array_add (found, (gpointer) nd);
else if (!g_strcmp0(nd->id, interface))
g_ptr_array_add (found, (gpointer) nd);
else if (!g_strcmp0(nd->match.original_name, interface))
g_ptr_array_add (found, (gpointer) nd);
}
if (found->len == 0 && driver != NULL) {
/* testing for driver matching is done via autopkgtest */
// LCOV_EXCL_START
g_hash_table_iter_init (&iter, netdefs);
while (g_hash_table_iter_next (&iter, &key, &value)) {
NetplanNetDefinition *nd = (NetplanNetDefinition *) value;
if (!g_strcmp0(nd->match.driver, driver))
g_ptr_array_add (found, (gpointer) nd);
}
// LCOV_EXCL_STOP
}
if (driver)
g_free (driver); // LCOV_EXCL_LINE
if (found->len != 1) {
goto exit_find;
}
else {
const NetplanNetDefinition *nd = (NetplanNetDefinition *)g_ptr_array_index (found, 0);
g_printf("id=%s, backend=%s, set_name=%s, match_name=%s, match_mac=%s, match_driver=%s\n",
nd->id,
netplan_backend_name(nd->backend),
nd->set_name,
nd->match.original_name,
nd->match.mac,
nd->match.driver);
}
ret = EXIT_SUCCESS;
exit_find:
g_ptr_array_free (found, TRUE);
return ret;
}
#define CHECK_CALL(call) {\
if (!call) {\
error_code = 1; \
fprintf(stderr, "%s\n", error->message); \
goto cleanup;\
}\
}
int main(int argc, char** argv)
{
NetplanError* error = NULL;
GOptionContext* opt_context;
/* are we being called as systemd generator? */
gboolean called_as_generator = (strstr(argv[0], "systemd/system-generators/") != NULL);
g_autofree char* generator_run_stamp = NULL;
glob_t gl;
int error_code = 0;
NetplanParser* npp = NULL;
NetplanState* np_state = NULL;
/* Parse CLI options */
opt_context = g_option_context_new(NULL);
if (called_as_generator)
g_option_context_set_help_enabled(opt_context, FALSE);
g_option_context_set_summary(opt_context, "Generate backend network configuration from netplan YAML definition.");
g_option_context_set_description(opt_context,
"This program reads the specified netplan YAML definition file(s)\n"
"or, if none are given, /etc/netplan/*.yaml.\n"
"It then generates the corresponding systemd-networkd, NetworkManager,\n"
"and udev configuration files in /run.");
g_option_context_add_main_entries(opt_context, options, NULL);
if (!g_option_context_parse(opt_context, &argc, &argv, &error)) {
fprintf(stderr, "failed to parse options: %s\n", error->message);
return 1;
}
if (called_as_generator) {
if (files == NULL || g_strv_length(files) != 3 || files[0] == NULL) {
g_fprintf(stderr, "%s can not be called directly, use 'netplan generate'.", argv[0]);
return 1;
}
generator_run_stamp = g_build_path(G_DIR_SEPARATOR_S, files[0], "netplan.stamp", NULL);
if (g_access(generator_run_stamp, F_OK) == 0) {
g_fprintf(stderr, "netplan generate already ran, remove %s to force re-run\n", generator_run_stamp);
return 0;
}
}
npp = netplan_parser_new();
/* Read all input files */
if (files && !called_as_generator) {
for (gchar** f = files; f && *f; ++f) {
CHECK_CALL(netplan_parser_load_yaml(npp, *f, &error));
}
} else
CHECK_CALL(netplan_parser_load_yaml_hierarchy(npp, rootdir, &error));
np_state = netplan_state_new();
CHECK_CALL(netplan_state_import_parser_results(np_state, npp, &error));
if (mapping_iface) {
if (np_state->netdefs)
error_code = find_interface(mapping_iface, np_state->netdefs);
else
error_code = 1;
goto cleanup;
}
/* Clean up generated config from previous runs */
netplan_networkd_cleanup(rootdir);
netplan_nm_cleanup(rootdir);
netplan_ovs_cleanup(rootdir);
netplan_sriov_cleanup(rootdir);
/* Generate backend specific configuration files from merged data. */
CHECK_CALL(netplan_state_finish_ovs_write(np_state, rootdir, &error)); // OVS cleanup unit is always written
if (np_state->netdefs) {
g_debug("Generating output files..");
for (GList* iterator = np_state->netdefs_ordered; iterator; iterator = iterator->next) {
NetplanNetDefinition* def = (NetplanNetDefinition*) iterator->data;
gboolean has_been_written = FALSE;
CHECK_CALL(netplan_netdef_write_networkd(np_state, def, rootdir, &has_been_written, &error));
any_networkd = any_networkd || has_been_written;
CHECK_CALL(netplan_netdef_write_ovs(np_state, def, rootdir, &has_been_written, &error));
CHECK_CALL(netplan_netdef_write_nm(np_state, def, rootdir, &has_been_written, &error));
any_nm = any_nm || has_been_written;
}
CHECK_CALL(netplan_state_finish_nm_write(np_state, rootdir, &error));
CHECK_CALL(netplan_state_finish_sriov_write(np_state, rootdir, &error));
/* We may have written .rules & .link files, thus we must
* invalidate udevd cache of its config as by default it only
* invalidates cache at most every 3 seconds. Not sure if this
* should live in `generate' or `apply', but it is confusing
* when udevd ignores just-in-time created rules files.
*/
reload_udevd();
}
/* Disable /usr/lib/NetworkManager/conf.d/10-globally-managed-devices.conf
* (which restricts NM to wifi and wwan) if "renderer: NetworkManager" is used anywhere */
if (netplan_state_get_backend(np_state) == NETPLAN_BACKEND_NM || any_nm)
g_string_free_to_file(g_string_new(NULL), rootdir, "/run/NetworkManager/conf.d/10-globally-managed-devices.conf", NULL);
if (called_as_generator) {
/* Ensure networkd starts if we have any configuration for it */
if (any_networkd)
enable_networkd(files[0]);
/* Leave a stamp file so that we don't regenerate the configuration
* multiple times and userspace can wait for it to finish */
FILE* f = fopen(generator_run_stamp, "w");
g_assert(f != NULL);
fclose(f);
} else if (check_called_just_in_time()) {
/* netplan-feature: generate-just-in-time */
/* When booting with cloud-init, network configuration
* might be provided just-in-time. Specifically after
* system-generators were executed, but before
* network.target is started. In such case, auxiliary
* units that netplan enables have not been included in
* the initial boot transaction. Detect such scenario and
* add all netplan units to the initial boot transaction.
*/
// LCOV_EXCL_START
/* covered via 'cloud-init' integration test */
if (any_networkd) {
start_unit_jit("systemd-networkd.socket");
start_unit_jit("systemd-networkd-wait-online.service");
start_unit_jit("systemd-networkd.service");
}
g_autofree char* glob_run = g_build_path(G_DIR_SEPARATOR_S,
rootdir ?: G_DIR_SEPARATOR_S,
"run/systemd/system/netplan-*.service",
NULL);
if (!glob(glob_run, 0, NULL, &gl)) {
for (size_t i = 0; i < gl.gl_pathc; ++i) {
gchar *unit_name = g_path_get_basename(gl.gl_pathv[i]);
start_unit_jit(unit_name);
g_free(unit_name);
}
}
// LCOV_EXCL_STOP
}
cleanup:
g_option_context_free(opt_context);
if (error)
g_error_free(error);
if (npp)
netplan_parser_clear(&npp);
if (np_state)
netplan_state_clear(&np_state);
return error_code;
}
netplan-0.107.1/src/meson.build 0000664 0000000 0000000 00000002422 14536110317 0016253 0 ustar 00root root 0000000 0000000 sources = files(
'abi_compat.c',
'error.c',
'names.c',
'netplan.c',
'networkd.c',
'nm.c',
'openvswitch.c',
'parse.c',
'parse-nm.c',
'sriov.c',
'types.c',
'util.c',
'validation.c')
linker_script = meson.project_source_root() / 'abicompat.lds'
libnetplan = library(
'netplan',
sources,
gnu_symbol_visibility: 'hidden',
link_args: ['-T', linker_script],
link_depends: linker_script,
dependencies: [glib, gio, yaml, uuid],
include_directories: inc,
soversion: '0.0',
install: true)
libexec_netplan = join_paths(get_option('libexecdir'), 'netplan')
executable(
'generate',
'generate.c',
include_directories: inc,
link_with: libnetplan,
dependencies: [glib, gio, yaml, uuid],
install_dir: libexec_netplan,
install: true)
meson.add_install_script(meson_make_symlink,
join_paths(get_option('prefix'), libexec_netplan, 'generate'),
join_paths(systemd_generator_dir, 'netplan'))
# FIXME: Drop legacy symlink after 0.107 is released:
# It's only around for legacy reasons, see netplan/cli/utils.py: get_generator_path()
meson.add_install_script(meson_make_symlink,
join_paths(get_option('prefix'), libexec_netplan, 'generate'),
join_paths('/', 'lib', 'netplan', 'generate'))
netplan-0.107.1/src/names.c 0000664 0000000 0000000 00000013664 14536110317 0015372 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Canonical, Ltd.
* Author: Simon Chopin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#include
#include "names.h"
#include "parse.h"
/* Non-static as we need it for ABI compatibility, see at the end of the file */
const char* const
netplan_backend_to_str[NETPLAN_BACKEND_MAX_] = {
[NETPLAN_BACKEND_NONE] = "none",
[NETPLAN_BACKEND_NETWORKD] = "networkd",
[NETPLAN_BACKEND_NM] = "NetworkManager",
[NETPLAN_BACKEND_OVS] = "OpenVSwitch",
};
static const char* const
netplan_wifi_mode_to_str[NETPLAN_WIFI_MODE_MAX_] = {
[NETPLAN_WIFI_MODE_INFRASTRUCTURE] = "infrastructure",
[NETPLAN_WIFI_MODE_ADHOC] = "adhoc",
[NETPLAN_WIFI_MODE_AP] = "ap",
[NETPLAN_WIFI_MODE_OTHER] = NULL,
};
static const char* const
netplan_def_type_to_str[NETPLAN_DEF_TYPE_MAX_] = {
[NETPLAN_DEF_TYPE_NONE] = NULL,
[NETPLAN_DEF_TYPE_ETHERNET] = "ethernets",
[NETPLAN_DEF_TYPE_WIFI] = "wifis",
[NETPLAN_DEF_TYPE_MODEM] = "modems",
[NETPLAN_DEF_TYPE_BRIDGE] = "bridges",
[NETPLAN_DEF_TYPE_BOND] = "bonds",
[NETPLAN_DEF_TYPE_VLAN] = "vlans",
[NETPLAN_DEF_TYPE_VRF] = "vrfs",
[NETPLAN_DEF_TYPE_TUNNEL] = "tunnels",
[NETPLAN_DEF_TYPE_DUMMY] = "dummy-devices", /* wokeignore:rule=dummy */
[NETPLAN_DEF_TYPE_VETH] = "virtual-ethernets",
[NETPLAN_DEF_TYPE_PORT] = "_ovs-ports",
[NETPLAN_DEF_TYPE_NM] = "nm-devices",
};
static const char* const
netplan_auth_key_management_type_to_str[NETPLAN_AUTH_KEY_MANAGEMENT_MAX] = {
[NETPLAN_AUTH_KEY_MANAGEMENT_NONE] = "none",
[NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK] = "psk",
[NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP] = "eap",
[NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSHA256] = "eap-sha256",
[NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSUITE_B_192] = "eap-suite-b-192",
[NETPLAN_AUTH_KEY_MANAGEMENT_WPA_SAE] = "sae",
[NETPLAN_AUTH_KEY_MANAGEMENT_8021X] = "802.1x",
};
static const char* const
netplan_auth_eap_method_to_str[NETPLAN_AUTH_EAP_METHOD_MAX] = {
[NETPLAN_AUTH_EAP_NONE] = NULL,
[NETPLAN_AUTH_EAP_TLS] = "tls",
[NETPLAN_AUTH_EAP_PEAP] = "peap",
[NETPLAN_AUTH_EAP_TTLS] = "ttls",
[NETPLAN_AUTH_EAP_LEAP] = "leap",
[NETPLAN_AUTH_EAP_PWD] = "pwd",
};
static const char* const
netplan_tunnel_mode_to_str[NETPLAN_TUNNEL_MODE_MAX_] = {
[NETPLAN_TUNNEL_MODE_UNKNOWN] = NULL,
[NETPLAN_TUNNEL_MODE_IPIP] = "ipip",
[NETPLAN_TUNNEL_MODE_GRE] = "gre",
[NETPLAN_TUNNEL_MODE_SIT] = "sit",
[NETPLAN_TUNNEL_MODE_ISATAP] = "isatap",
[NETPLAN_TUNNEL_MODE_VTI] = "vti",
[NETPLAN_TUNNEL_MODE_IP6IP6] = "ip6ip6",
[NETPLAN_TUNNEL_MODE_IPIP6] = "ipip6",
[NETPLAN_TUNNEL_MODE_IP6GRE] = "ip6gre",
[NETPLAN_TUNNEL_MODE_VTI6] = "vti6",
[NETPLAN_TUNNEL_MODE_GRETAP] = "gretap",
[NETPLAN_TUNNEL_MODE_IP6GRETAP] = "ip6gretap",
[NETPLAN_TUNNEL_MODE_VXLAN] = "vxlan",
[NETPLAN_TUNNEL_MODE_WIREGUARD] = "wireguard",
};
static const char* const
netplan_addr_gen_mode_to_str[NETPLAN_ADDRGEN_MAX] = {
[NETPLAN_ADDRGEN_DEFAULT] = NULL,
[NETPLAN_ADDRGEN_EUI64] = "eui64",
[NETPLAN_ADDRGEN_STABLEPRIVACY] = "stable-privacy"
};
static const char* const
netplan_infiniband_mode_to_str[NETPLAN_IB_MODE_MAX_] = {
[NETPLAN_IB_MODE_KERNEL] = NULL,
[NETPLAN_IB_MODE_DATAGRAM] = "datagram",
[NETPLAN_IB_MODE_CONNECTED] = "connected"
};
static const char* const
netplan_key_flags_to_str[NETPLAN_KEY_FLAG_MAX_] = {
[NETPLAN_KEY_FLAG_NONE] = NULL,
[NETPLAN_KEY_FLAG_AGENT_OWNED] = "agent-owned",
[NETPLAN_KEY_FLAG_NOT_SAVED] = "not-saved",
[NETPLAN_KEY_FLAG_NOT_REQUIRED] = "not-required",
};
#define NAME_FUNCTION(_radical, _type) const char *netplan_ ## _radical ## _name( _type val) \
{ \
return (val < sizeof(netplan_ ## _radical ## _to_str) / sizeof(char *)) ? netplan_ ## _radical ## _to_str [val] : NULL; \
}
/* @num_flags needs to account for the 0x0 value (index 0), which doesn't
* represent any flag, but still exists. So subtract 1 from the array length. */
#define NAME_FUNCTION_FLAGS(_radical) const char *netplan_ ## _radical ## _name( NetplanFlags val) \
{ \
size_t num_flags = sizeof(netplan_ ## _radical ## _to_str) / sizeof(char *) - 1; \
return (val <= 1 << (num_flags - 1)) ? netplan_ ## _radical ## _to_str [__builtin_ffs(val)] : NULL; \
}
NAME_FUNCTION(backend, NetplanBackend);
NAME_FUNCTION(def_type, NetplanDefType);
NAME_FUNCTION(auth_key_management_type, NetplanAuthKeyManagementType);
NAME_FUNCTION(auth_eap_method, NetplanAuthEAPMethod);
NAME_FUNCTION(tunnel_mode, NetplanTunnelMode);
NAME_FUNCTION(addr_gen_mode, NetplanAddrGenMode);
NAME_FUNCTION(wifi_mode, NetplanWifiMode);
NAME_FUNCTION(infiniband_mode, NetplanInfinibandMode);
NAME_FUNCTION(key_flags, NetplanKeyFlags);
NAME_FUNCTION_FLAGS(vxlan_notification);
NAME_FUNCTION_FLAGS(vxlan_checksum);
NAME_FUNCTION_FLAGS(vxlan_extension);
#define ENUM_FUNCTION(_radical, _type) _type netplan_ ## _radical ## _from_name(const char* val) \
{ \
for (size_t i = 0; i < sizeof(netplan_ ## _radical ## _to_str); ++i) { \
if (g_strcmp0(val, netplan_ ## _radical ## _to_str[i]) == 0) \
return i; \
} \
return NETPLAN_DEF_TYPE_NONE; \
}
ENUM_FUNCTION(def_type, NetplanDefType);
/* ABI compatibility definitions */
NETPLAN_ABI const char*
tunnel_mode_to_string(NetplanTunnelMode val) __attribute__ ((alias ("netplan_tunnel_mode_name")));
NETPLAN_ABI extern const char*
netplan_backend_to_name __attribute__((alias("netplan_backend_to_str")));
netplan-0.107.1/src/names.h 0000664 0000000 0000000 00000004617 14536110317 0015375 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Canonical, Ltd.
* Author: Simon Chopin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#pragma once
#include "netplan.h"
#include "types-internal.h"
NETPLAN_INTERNAL const char*
netplan_backend_name(NetplanBackend val);
NETPLAN_INTERNAL const char*
netplan_def_type_name(NetplanDefType val);
const char*
netplan_auth_key_management_type_name(NetplanAuthKeyManagementType val);
const char*
netplan_auth_eap_method_name(NetplanAuthEAPMethod val);
const char*
netplan_tunnel_mode_name(NetplanTunnelMode val);
const char*
netplan_addr_gen_mode_name(NetplanAddrGenMode val);
const char*
netplan_wifi_mode_name(NetplanWifiMode val);
const char*
netplan_infiniband_mode_name(NetplanInfinibandMode val);
const char*
netplan_key_flags_name(NetplanKeyFlags val);
const char*
netplan_vxlan_notification_name(int val);
const char*
netplan_vxlan_checksum_name(int val);
const char*
netplan_vxlan_extension_name(int val);
NetplanDefType
netplan_def_type_from_name(const char* val);
/* Netplan flag names */
static const char* const
netplan_vxlan_notification_to_str[] = {
[__builtin_ffs(NETPLAN_VXLAN_NOTIFICATION_L2_MISS)] = "l2-miss",
[__builtin_ffs(NETPLAN_VXLAN_NOTIFICATION_L3_MISS)] = "l3-miss",
};
static const char* const
netplan_vxlan_checksum_to_str[] = {
[__builtin_ffs(NETPLAN_VXLAN_CHECKSUM_UDP)] = "udp",
[__builtin_ffs(NETPLAN_VXLAN_CHECKSUM_ZERO_UDP6_TX)] = "zero-udp6-tx",
[__builtin_ffs(NETPLAN_VXLAN_CHECKSUM_ZERO_UDP6_RX)] = "zero-udp6-rx",
[__builtin_ffs(NETPLAN_VXLAN_CHECKSUM_REMOTE_TX)] = "remote-tx",
[__builtin_ffs(NETPLAN_VXLAN_CHECKSUM_REMOTE_RX)] = "remote-rx",
};
static const char* const
netplan_vxlan_extension_to_str[] = {
[__builtin_ffs(NETPLAN_VXLAN_EXTENSION_GROUP_POLICY)] = "group-policy",
[__builtin_ffs(NETPLAN_VXLAN_EXTENSION_GENERIC_PROTOCOL)] = "generic-protocol",
};
netplan-0.107.1/src/netplan.c 0000664 0000000 0000000 00000162247 14536110317 0015732 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021-2023 Canonical, Ltd.
* Author: Lukas Märdian
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#include
#include
#include
#include
#include
#include
#include "netplan.h"
#include "parse.h"
#include "types-internal.h"
#include "yaml-helpers.h"
#include "util-internal.h"
#include "names.h"
gchar *tmp = NULL;
#define DIRTY(_def, _data) \
((_def)->_private && \
(_def)->_private->dirty_fields && \
g_hash_table_contains((_def)->_private->dirty_fields, &(_data)))
#define DIRTY_REF(_def, _data_ref) \
((_def)->_private && \
(_def)->_private->dirty_fields && \
g_hash_table_contains((_def)->_private->dirty_fields, _data_ref))
#define YAML_STRING(_def, event_ptr, emitter_ptr, key, value_ptr) {\
if (value_ptr) { \
YAML_SCALAR_PLAIN(event_ptr, emitter_ptr, key); \
YAML_SCALAR_QUOTED(event_ptr, emitter_ptr, value_ptr); \
} else if DIRTY(_def, value_ptr) { \
YAML_SCALAR_PLAIN(event_ptr, emitter_ptr, key); \
YAML_NULL_PLAIN(event_ptr, emitter_ptr); \
} \
}\
#define YAML_STRING_PLAIN(_def, event_ptr, emitter_ptr, key, value_ptr) {\
if (value_ptr) { \
YAML_SCALAR_PLAIN(event_ptr, emitter_ptr, key); \
YAML_SCALAR_PLAIN(event_ptr, emitter_ptr, value_ptr); \
} else if DIRTY(_def, value_ptr) { \
YAML_SCALAR_PLAIN(event_ptr, emitter_ptr, key); \
YAML_NULL_PLAIN(event_ptr, emitter_ptr); \
} \
}\
#define YAML_UINT_DEFAULT(_def, event_ptr, emitter_ptr, key, value, default_value) {\
if (value != default_value) { \
_YAML_UINT(event_ptr, emitter_ptr, key, value); \
} else if DIRTY(_def, value) { \
YAML_SCALAR_PLAIN(event_ptr, emitter_ptr, key); \
YAML_NULL_PLAIN(event_ptr, emitter_ptr); \
} \
}\
#define YAML_UINT_0(_def, event_ptr, emitter_ptr, key, value) \
YAML_UINT_DEFAULT(_def, event_ptr, emitter_ptr, key, value, 0);
#define YAML_BOOL_TRUE(_def, event_ptr, emitter_ptr, key, value) {\
if (value) { \
YAML_NONNULL_STRING_PLAIN(event_ptr, emitter_ptr, key, "true"); \
} else if DIRTY(_def, value) { \
YAML_NONNULL_STRING_PLAIN(event_ptr, emitter_ptr, key, "false"); \
} \
}\
#define YAML_BOOL_FALSE(_def, event_ptr, emitter_ptr, key, value) {\
if (!value) { \
YAML_NONNULL_STRING_PLAIN(event_ptr, emitter_ptr, key, "false"); \
} else if DIRTY(_def, value) { \
YAML_NONNULL_STRING_PLAIN(event_ptr, emitter_ptr, key, "true"); \
} \
}\
#define YAML_BOOL_TRISTATE(_def, event_ptr, emitter_ptr, key, value) {\
if (value == NETPLAN_TRISTATE_TRUE) { \
YAML_NONNULL_STRING_PLAIN(event_ptr, emitter_ptr, key, "true"); \
} else if (value == NETPLAN_TRISTATE_FALSE) { \
YAML_NONNULL_STRING_PLAIN(event_ptr, emitter_ptr, key, "false"); \
} \
}
#define DIRTY_COMPLEX(_def, _data) complex_object_is_dirty(_def, (char*)(&_data), sizeof(_data))
static gboolean
write_match(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def)
{
YAML_SCALAR_PLAIN(event, emitter, "match");
YAML_MAPPING_OPEN(event, emitter);
YAML_NONNULL_STRING(event, emitter, "name", def->match.original_name);
YAML_NONNULL_STRING(event, emitter, "macaddress", def->match.mac)
if (def->match.driver && strchr(def->match.driver, '\t')) {
gchar **split = g_strsplit(def->match.driver, "\t", 0);
YAML_SCALAR_PLAIN(event, emitter, "driver");
YAML_SEQUENCE_OPEN(event, emitter);
for (unsigned i = 0; split[i]; ++i)
YAML_SCALAR_QUOTED(event, emitter, split[i]);
YAML_SEQUENCE_CLOSE(event, emitter);
g_strfreev(split);
} else
YAML_NONNULL_STRING(event, emitter, "driver", def->match.driver);
YAML_MAPPING_CLOSE(event, emitter);
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_auth(yaml_event_t* event, yaml_emitter_t* emitter, NetplanAuthenticationSettings auth)
{
YAML_SCALAR_PLAIN(event, emitter, "auth");
YAML_MAPPING_OPEN(event, emitter);
YAML_NONNULL_STRING(event, emitter, "key-management", netplan_auth_key_management_type_name(auth.key_management));
YAML_NONNULL_STRING(event, emitter, "method", netplan_auth_eap_method_name(auth.eap_method));
YAML_NONNULL_STRING(event, emitter, "anonymous-identity", auth.anonymous_identity);
YAML_NONNULL_STRING(event, emitter, "identity", auth.identity);
YAML_NONNULL_STRING(event, emitter, "ca-certificate", auth.ca_certificate);
YAML_NONNULL_STRING(event, emitter, "client-certificate", auth.client_certificate);
YAML_NONNULL_STRING(event, emitter, "client-key", auth.client_key);
YAML_NONNULL_STRING(event, emitter, "client-key-password", auth.client_key_password);
YAML_NONNULL_STRING(event, emitter, "phase2-auth", auth.phase2_auth);
if (!auth.password && auth.psk) {
YAML_NONNULL_STRING(event, emitter, "password", auth.psk);
} else {
YAML_NONNULL_STRING(event, emitter, "password", auth.password);
}
YAML_MAPPING_CLOSE(event, emitter);
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_bond_params(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def)
{
if (DIRTY(def, def->bond_params)
|| def->bond_params.mode
|| def->bond_params.monitor_interval
|| def->bond_params.up_delay
|| def->bond_params.down_delay
|| def->bond_params.lacp_rate
|| def->bond_params.transmit_hash_policy
|| def->bond_params.selection_logic
|| def->bond_params.arp_validate
|| def->bond_params.arp_all_targets
|| def->bond_params.fail_over_mac_policy
|| def->bond_params.primary_reselect_policy
|| def->bond_params.learn_interval
|| def->bond_params.arp_interval
|| def->bond_params.primary_member
|| def->bond_params.min_links
|| def->bond_params.all_members_active
|| def->bond_params.gratuitous_arp
|| def->bond_params.packets_per_member
|| def->bond_params.resend_igmp
|| def->bond_params.arp_ip_targets) {
YAML_SCALAR_PLAIN(event, emitter, "parameters");
YAML_MAPPING_OPEN(event, emitter);
YAML_STRING(def, event, emitter, "mode", def->bond_params.mode);
YAML_STRING(def, event, emitter, "mii-monitor-interval", def->bond_params.monitor_interval);
YAML_STRING(def, event, emitter, "up-delay", def->bond_params.up_delay);
YAML_STRING(def, event, emitter, "down-delay", def->bond_params.down_delay);
YAML_STRING(def, event, emitter, "lacp-rate", def->bond_params.lacp_rate);
YAML_STRING(def, event, emitter, "transmit-hash-policy", def->bond_params.transmit_hash_policy);
YAML_STRING(def, event, emitter, "ad-select", def->bond_params.selection_logic);
YAML_STRING(def, event, emitter, "arp-validate", def->bond_params.arp_validate);
YAML_STRING(def, event, emitter, "arp-all-targets", def->bond_params.arp_all_targets);
YAML_STRING(def, event, emitter, "fail-over-mac-policy", def->bond_params.fail_over_mac_policy);
YAML_STRING(def, event, emitter, "primary-reselect-policy", def->bond_params.primary_reselect_policy);
YAML_STRING(def, event, emitter, "learn-packet-interval", def->bond_params.learn_interval);
YAML_STRING(def, event, emitter, "arp-interval", def->bond_params.arp_interval);
YAML_STRING(def, event, emitter, "primary", def->bond_params.primary_member);
YAML_UINT_0(def, event, emitter, "min-links", def->bond_params.min_links);
YAML_BOOL_TRUE(def, event, emitter, "all-members-active", def->bond_params.all_members_active);
YAML_UINT_0(def, event, emitter, "gratuitous-arp", def->bond_params.gratuitous_arp);
YAML_UINT_0(def, event, emitter, "packets-per-member", def->bond_params.packets_per_member);
YAML_UINT_0(def, event, emitter, "resend-igmp", def->bond_params.resend_igmp);
if (def->bond_params.arp_ip_targets || DIRTY(def, def->bond_params.arp_ip_targets)) {
GArray* arr = def->bond_params.arp_ip_targets;
YAML_SCALAR_PLAIN(event, emitter, "arp-ip-targets");
YAML_SEQUENCE_OPEN(event, emitter);
if (arr)
for (unsigned i = 0; i < arr->len; ++i)
YAML_SCALAR_PLAIN(event, emitter, g_array_index(arr, char*, i));
YAML_SEQUENCE_CLOSE(event, emitter);
}
YAML_MAPPING_CLOSE(event, emitter);
}
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_vxlan(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def)
{
if (def->type == NETPLAN_DEF_TYPE_TUNNEL && def->tunnel.mode == NETPLAN_TUNNEL_MODE_VXLAN) {
g_assert(def->vxlan);
YAML_UINT_0(def, event, emitter, "id", def->vxlan->vni);
if (def->vxlan->link)
YAML_STRING(def, event, emitter, "link", def->vxlan->link->id);
if (def->vxlan->source_port_min && def->vxlan->source_port_max) {
YAML_SCALAR_PLAIN(event, emitter, "port-range");
YAML_SEQUENCE_OPEN(event, emitter);
tmp = g_strdup_printf("%u", def->vxlan->source_port_min);
YAML_SCALAR_PLAIN(event, emitter, tmp);
g_free(tmp);
tmp = g_strdup_printf("%u", def->vxlan->source_port_max);
YAML_SCALAR_PLAIN(event, emitter, tmp);
g_free(tmp);
YAML_SEQUENCE_CLOSE(event, emitter);
}
YAML_UINT_DEFAULT(def, event, emitter, "flow-label", def->vxlan->flow_label, G_MAXUINT);
YAML_UINT_0(def, event, emitter, "limit", def->vxlan->limit);
YAML_UINT_0(def, event, emitter, "type-of-service", def->vxlan->tos);
YAML_UINT_0(def, event, emitter, "ageing", def->vxlan->ageing);
YAML_BOOL_TRISTATE(def, event, emitter, "mac-learning", def->vxlan->mac_learning);
YAML_BOOL_TRISTATE(def, event, emitter, "arp-proxy", def->vxlan->arp_proxy);
YAML_BOOL_TRISTATE(def, event, emitter, "short-circuit", def->vxlan->short_circuit);
YAML_BOOL_TRISTATE(def, event, emitter, "do-not-fragment", def->vxlan->do_not_fragment);
if (def->vxlan->notifications) {
YAML_SCALAR_PLAIN(event, emitter, "notifications");
YAML_SEQUENCE_OPEN(event, emitter);
int f = def->vxlan->notifications;
const char* (*fn)(int) = &netplan_vxlan_notification_name;
YAML_FLAG(event, emitter, NETPLAN_VXLAN_NOTIFICATION_L2_MISS, f, (*fn));
YAML_FLAG(event, emitter, NETPLAN_VXLAN_NOTIFICATION_L3_MISS, f, (*fn));
YAML_SEQUENCE_CLOSE(event, emitter);
}
if (def->vxlan->checksums) {
YAML_SCALAR_PLAIN(event, emitter, "checksums");
YAML_SEQUENCE_OPEN(event, emitter);
int f = def->vxlan->checksums;
const char* (*fn)(int) = &netplan_vxlan_checksum_name;
YAML_FLAG(event, emitter, NETPLAN_VXLAN_CHECKSUM_UDP, f, (*fn));
YAML_FLAG(event, emitter, NETPLAN_VXLAN_CHECKSUM_ZERO_UDP6_TX, f, (*fn));
YAML_FLAG(event, emitter, NETPLAN_VXLAN_CHECKSUM_ZERO_UDP6_RX, f, (*fn));
YAML_FLAG(event, emitter, NETPLAN_VXLAN_CHECKSUM_REMOTE_TX, f, (*fn));
YAML_FLAG(event, emitter, NETPLAN_VXLAN_CHECKSUM_REMOTE_RX, f, (*fn));
YAML_SEQUENCE_CLOSE(event, emitter);
}
if (def->vxlan->extensions) {
YAML_SCALAR_PLAIN(event, emitter, "extensions");
YAML_SEQUENCE_OPEN(event, emitter);
int f = def->vxlan->extensions;
const char* (*fn)(int) = &netplan_vxlan_extension_name;
YAML_FLAG(event, emitter, NETPLAN_VXLAN_EXTENSION_GROUP_POLICY, f, (*fn));
YAML_FLAG(event, emitter, NETPLAN_VXLAN_EXTENSION_GENERIC_PROTOCOL, f, (*fn));
YAML_SEQUENCE_CLOSE(event, emitter);
}
}
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_bridge_params(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def, const GArray *interfaces)
{
if (def->custom_bridging || DIRTY_COMPLEX(def, def->bridge_params)) {
gboolean has_path_cost = FALSE;
gboolean has_port_priority = FALSE;
for (unsigned i = 0; i < interfaces->len; ++i) {
NetplanNetDefinition *nd = g_array_index(interfaces, NetplanNetDefinition*, i);
has_path_cost = has_path_cost || !!nd->bridge_params.path_cost;
has_port_priority = has_port_priority || !!nd->bridge_params.port_priority;
if (has_path_cost && has_port_priority)
break; /* no need to continue this check */
}
YAML_SCALAR_PLAIN(event, emitter, "parameters");
YAML_MAPPING_OPEN(event, emitter);
YAML_STRING(def, event, emitter, "ageing-time", def->bridge_params.ageing_time);
YAML_STRING(def, event, emitter, "forward-delay", def->bridge_params.forward_delay);
YAML_STRING(def, event, emitter, "hello-time", def->bridge_params.hello_time);
YAML_STRING(def, event, emitter, "max-age", def->bridge_params.max_age);
YAML_UINT_0(def, event, emitter, "priority", def->bridge_params.priority);
YAML_BOOL_FALSE(def, event, emitter, "stp", def->bridge_params.stp);
if (has_port_priority) {
YAML_SCALAR_PLAIN(event, emitter, "port-priority");
YAML_MAPPING_OPEN(event, emitter);
for (unsigned i = 0; i < interfaces->len; ++i) {
NetplanNetDefinition *nd = g_array_index(interfaces, NetplanNetDefinition*, i);
YAML_UINT_0(nd, event, emitter, nd->id, nd->bridge_params.port_priority);
}
YAML_MAPPING_CLOSE(event, emitter);
}
if (has_path_cost) {
YAML_SCALAR_PLAIN(event, emitter, "path-cost");
YAML_MAPPING_OPEN(event, emitter);
for (unsigned i = 0; i < interfaces->len; ++i) {
NetplanNetDefinition *nd = g_array_index(interfaces, NetplanNetDefinition*, i);
YAML_UINT_0(nd, event, emitter, nd->id, nd->bridge_params.path_cost);
}
YAML_MAPPING_CLOSE(event, emitter);
}
YAML_MAPPING_CLOSE(event, emitter);
}
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_modem_params(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def)
{
/* some modem settings to auto-detect GSM vs CDMA connections */
YAML_BOOL_TRUE(def, event, emitter, "auto-config", def->modem_params.auto_config);
YAML_NONNULL_STRING(event, emitter, "apn", def->modem_params.apn);
YAML_NONNULL_STRING(event, emitter, "device-id", def->modem_params.device_id);
YAML_NONNULL_STRING(event, emitter, "network-id", def->modem_params.network_id);
YAML_NONNULL_STRING(event, emitter, "pin", def->modem_params.pin);
YAML_NONNULL_STRING(event, emitter, "sim-id", def->modem_params.sim_id);
YAML_NONNULL_STRING(event, emitter, "sim-operator-id", def->modem_params.sim_operator_id);
YAML_NONNULL_STRING(event, emitter, "username", def->modem_params.username);
YAML_NONNULL_STRING(event, emitter, "password", def->modem_params.password);
YAML_NONNULL_STRING(event, emitter, "number", def->modem_params.number);
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
typedef struct {
yaml_event_t* event;
yaml_emitter_t* emitter;
} _passthrough_handler_data;
static void
_passthrough_handler(GQuark key_id, gpointer value, gpointer user_data)
{
_passthrough_handler_data *d = user_data;
const gchar* key = g_quark_to_string(key_id);
YAML_NONNULL_STRING(d->event, d->emitter, key, value);
err_path: return; // LCOV_EXCL_LINE
}
static gboolean
write_backend_settings(yaml_event_t* event, yaml_emitter_t* emitter, NetplanBackendSettings s) {
if (s.uuid || s.name || s.passthrough) {
YAML_SCALAR_PLAIN(event, emitter, "networkmanager");
YAML_MAPPING_OPEN(event, emitter);
YAML_NONNULL_STRING(event, emitter, "uuid", s.uuid);
YAML_NONNULL_STRING(event, emitter, "name", s.name);
if (s.passthrough) {
YAML_SCALAR_PLAIN(event, emitter, "passthrough");
YAML_MAPPING_OPEN(event, emitter);
_passthrough_handler_data d;
d.event = event;
d.emitter = emitter;
g_datalist_foreach(&s.passthrough, _passthrough_handler, &d);
YAML_MAPPING_CLOSE(event, emitter);
}
YAML_MAPPING_CLOSE(event, emitter);
}
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_access_points(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def)
{
NetplanWifiAccessPoint* ap = NULL;
GHashTableIter iter;
gpointer key, value;
YAML_SCALAR_PLAIN(event, emitter, "access-points");
YAML_MAPPING_OPEN(event, emitter);
g_hash_table_iter_init(&iter, def->access_points);
while (g_hash_table_iter_next(&iter, &key, &value)) {
ap = value;
YAML_SCALAR_QUOTED(event, emitter, ap->ssid);
YAML_MAPPING_OPEN(event, emitter);
YAML_BOOL_TRUE(def, event, emitter, "hidden", ap->hidden);
YAML_STRING(def, event, emitter, "bssid", ap->bssid);
if (ap->band == NETPLAN_WIFI_BAND_5) {
YAML_NONNULL_STRING(event, emitter, "band", "5GHz");
} else if (ap->band == NETPLAN_WIFI_BAND_24) {
YAML_NONNULL_STRING(event, emitter, "band", "2.4GHz");
}
YAML_UINT_0(def, event, emitter, "channel", ap->channel);
if (ap->auth.psk && !_is_auth_key_management_psk(&ap->auth))
YAML_NONNULL_STRING(event, emitter, "password", ap->auth.psk);
if (ap->has_auth || DIRTY(def, ap->auth))
write_auth(event, emitter, ap->auth);
if (ap->mode != NETPLAN_WIFI_MODE_INFRASTRUCTURE || DIRTY(def, ap->mode))
YAML_NONNULL_STRING(event, emitter, "mode", netplan_wifi_mode_name(ap->mode));
if (!write_backend_settings(event, emitter, ap->backend_settings)) goto err_path;
YAML_MAPPING_CLOSE(event, emitter);
}
YAML_MAPPING_CLOSE(event, emitter);
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_addresses(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def)
{
YAML_SCALAR_PLAIN(event, emitter, "addresses");
YAML_SEQUENCE_OPEN(event, emitter);
if (def->address_options) {
for (unsigned i = 0; i < def->address_options->len; ++i) {
NetplanAddressOptions *opts = g_array_index(def->address_options, NetplanAddressOptions*, i);
YAML_MAPPING_OPEN(event, emitter);
YAML_SCALAR_QUOTED(event, emitter, opts->address);
YAML_MAPPING_OPEN(event, emitter);
YAML_NONNULL_STRING(event, emitter, "label", opts->label);
YAML_NONNULL_STRING(event, emitter, "lifetime", opts->lifetime);
YAML_MAPPING_CLOSE(event, emitter);
YAML_MAPPING_CLOSE(event, emitter);
}
}
if (def->ip4_addresses) {
for (unsigned i = 0; i < def->ip4_addresses->len; ++i)
YAML_SCALAR_QUOTED(event, emitter, g_array_index(def->ip4_addresses, char*, i));
}
if (def->ip6_addresses) {
for (unsigned i = 0; i < def->ip6_addresses->len; ++i)
YAML_SCALAR_QUOTED(event, emitter, g_array_index(def->ip6_addresses, char*, i));
}
YAML_SEQUENCE_CLOSE(event, emitter);
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_nameservers(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def)
{
YAML_SCALAR_PLAIN(event, emitter, "nameservers");
YAML_MAPPING_OPEN(event, emitter);
if (def->ip4_nameservers || def->ip6_nameservers){
YAML_SCALAR_PLAIN(event, emitter, "addresses");
YAML_SEQUENCE_OPEN(event, emitter);
if (def->ip4_nameservers) {
for (unsigned i = 0; i < def->ip4_nameservers->len; ++i)
YAML_SCALAR_PLAIN(event, emitter, g_array_index(def->ip4_nameservers, char*, i));
}
if (def->ip6_nameservers) {
for (unsigned i = 0; i < def->ip6_nameservers->len; ++i)
YAML_SCALAR_PLAIN(event, emitter, g_array_index(def->ip6_nameservers, char*, i));
}
YAML_SEQUENCE_CLOSE(event, emitter);
}
if (def->search_domains || DIRTY(def, def->search_domains)){
YAML_SCALAR_PLAIN(event, emitter, "search");
YAML_SEQUENCE_OPEN(event, emitter);
if (def->search_domains) {
for (unsigned i = 0; i < def->search_domains->len; ++i)
YAML_SCALAR_PLAIN(event, emitter, g_array_index(def->search_domains, char*, i));
}
YAML_SEQUENCE_CLOSE(event, emitter);
}
YAML_MAPPING_CLOSE(event, emitter);
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_dhcp_overrides(yaml_event_t* event, yaml_emitter_t* emitter, const char* key, const NetplanNetDefinition* def, const NetplanDHCPOverrides* data)
{
if (DIRTY_COMPLEX(def, *data)
|| !data->use_dns
|| !data->use_ntp
|| !data->send_hostname
|| !data->use_hostname
|| !data->use_mtu
|| !data->use_routes
|| data->use_domains
|| data->hostname
|| data->metric != NETPLAN_METRIC_UNSPEC) {
YAML_SCALAR_PLAIN(event, emitter, key);
YAML_MAPPING_OPEN(event, emitter);
YAML_BOOL_FALSE(def, event, emitter, "use-dns", data->use_dns);
YAML_BOOL_FALSE(def, event, emitter, "use-ntp", data->use_ntp);
YAML_BOOL_FALSE(def, event, emitter, "send-hostname", data->send_hostname);
YAML_BOOL_FALSE(def, event, emitter, "use-hostname", data->use_hostname);
YAML_BOOL_FALSE(def, event, emitter, "use-mtu", data->use_mtu);
YAML_BOOL_FALSE(def, event, emitter, "use-routes", data->use_routes);
YAML_STRING_PLAIN(def, event, emitter, "use-domains", data->use_domains);
YAML_STRING(def, event, emitter, "hostname", data->hostname);
YAML_UINT_DEFAULT(def, event, emitter, "route-metric", data->metric, NETPLAN_METRIC_UNSPEC);
YAML_MAPPING_CLOSE(event, emitter);
}
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_tunnel_settings(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def)
{
YAML_NONNULL_STRING(event, emitter, "mode", netplan_tunnel_mode_name(def->tunnel.mode));
YAML_STRING(def, event, emitter, "local", def->tunnel.local_ip);
YAML_STRING(def, event, emitter, "remote", def->tunnel.remote_ip);
YAML_UINT_0(def, event, emitter, "mark", def->tunnel.fwmark);
YAML_UINT_0(def, event, emitter, "port", def->tunnel.port);
YAML_UINT_0(def, event, emitter, "ttl", def->tunnel_ttl);
/* VXLAN settings */
write_vxlan(event, emitter, def);
if (def->tunnel.input_key || def->tunnel.output_key || def->tunnel.private_key || def->tunnel_private_key_flags) {
if ( g_strcmp0(def->tunnel.input_key, def->tunnel.output_key) == 0
&& g_strcmp0(def->tunnel.input_key, def->tunnel.private_key) == 0
&& def->tunnel_private_key_flags == 0) {
/* use short form if all keys are the same */
YAML_STRING(def, event, emitter, "key", def->tunnel.input_key);
} else {
YAML_SCALAR_PLAIN(event, emitter, "keys");
YAML_MAPPING_OPEN(event, emitter);
YAML_STRING(def, event, emitter, "input", def->tunnel.input_key);
YAML_STRING(def, event, emitter, "output", def->tunnel.output_key);
YAML_STRING(def, event, emitter, "private", def->tunnel.private_key);
if (def->tunnel_private_key_flags > 0) {
YAML_SCALAR_PLAIN(event, emitter, "private-key-flags");
YAML_SEQUENCE_OPEN(event, emitter);
for(int i = 1; i < NETPLAN_KEY_FLAG_MAX_; i <<= 1) {
if (def->tunnel_private_key_flags & i)
YAML_SCALAR_PLAIN(event, emitter, netplan_key_flags_name(i));
}
YAML_SEQUENCE_CLOSE(event, emitter);
}
YAML_MAPPING_CLOSE(event, emitter);
}
}
/* Wireguard peers */
if (def->wireguard_peers && def->wireguard_peers->len > 0) {
YAML_SCALAR_PLAIN(event, emitter, "peers");
YAML_SEQUENCE_OPEN(event, emitter);
for (unsigned i = 0; i < def->wireguard_peers->len; ++i) {
NetplanWireguardPeer *peer = g_array_index(def->wireguard_peers, NetplanWireguardPeer*, i);
YAML_MAPPING_OPEN(event, emitter);
YAML_STRING(def, event, emitter, "endpoint", peer->endpoint);
YAML_UINT_0(def, event, emitter, "keepalive", peer->keepalive);
if (peer->public_key || peer->preshared_key) {
YAML_SCALAR_PLAIN(event, emitter, "keys");
YAML_MAPPING_OPEN(event, emitter);
YAML_STRING(def, event, emitter, "public", peer->public_key);
YAML_STRING(def, event, emitter, "shared", peer->preshared_key);
YAML_MAPPING_CLOSE(event, emitter);
}
if (peer->allowed_ips && peer->allowed_ips->len > 0) {
YAML_SCALAR_PLAIN(event, emitter, "allowed-ips");
YAML_SEQUENCE_OPEN(event, emitter);
for (unsigned i = 0; i < peer->allowed_ips->len; ++i) {
char *ip = g_array_index(peer->allowed_ips, char*, i);
YAML_SCALAR_QUOTED(event, emitter, ip);
}
YAML_SEQUENCE_CLOSE(event, emitter);
}
YAML_MAPPING_CLOSE(event, emitter);
}
YAML_SEQUENCE_CLOSE(event, emitter);
}
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_routes(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def)
{
if (def->routes && def->routes->len > 0) {
YAML_SCALAR_PLAIN(event, emitter, "routes");
YAML_SEQUENCE_OPEN(event, emitter);
for (unsigned i = 0; i < def->routes->len; ++i) {
YAML_MAPPING_OPEN(event, emitter);
NetplanIPRoute *r = g_array_index(def->routes, NetplanIPRoute*, i);
if (r->type && g_strcmp0(r->type, "unicast") != 0)
YAML_NONNULL_STRING(event, emitter, "type", r->type);
if (r->scope && g_strcmp0(r->scope, "global") != 0)
YAML_NONNULL_STRING(event, emitter, "scope", r->scope);
YAML_UINT_DEFAULT(def, event, emitter, "metric", r->metric, NETPLAN_METRIC_UNSPEC);
/* VRF devices use the VRF routing table implicitly */
if (def->type != NETPLAN_DEF_TYPE_VRF)
YAML_UINT_DEFAULT(def, event, emitter, "table", r->table, NETPLAN_ROUTE_TABLE_UNSPEC);
YAML_UINT_0(def, event, emitter, "mtu", r->mtubytes);
YAML_UINT_0(def, event, emitter, "congestion-window", r->congestion_window);
YAML_UINT_0(def, event, emitter, "advertised-receive-window", r->advertised_receive_window);
YAML_BOOL_TRUE(def, event, emitter, "on-link", r->onlink);
YAML_STRING(def, event, emitter, "from", r->from);
YAML_STRING(def, event, emitter, "to", r->to);
YAML_STRING(def, event, emitter, "via", r->via);
YAML_MAPPING_CLOSE(event, emitter);
}
YAML_SEQUENCE_CLOSE(event, emitter);
}
if (def->ip_rules && def->ip_rules->len > 0) {
YAML_SCALAR_PLAIN(event, emitter, "routing-policy");
YAML_SEQUENCE_OPEN(event, emitter);
for (unsigned i = 0; i < def->ip_rules->len; ++i) {
NetplanIPRule *r = g_array_index(def->ip_rules, NetplanIPRule*, i);
YAML_MAPPING_OPEN(event, emitter);
/* VRF devices use the VRF routing table implicitly */
if (def->type != NETPLAN_DEF_TYPE_VRF)
YAML_UINT_DEFAULT(def, event, emitter, "table", r->table, NETPLAN_ROUTE_TABLE_UNSPEC);
YAML_UINT_DEFAULT(def, event, emitter, "priority", r->priority, NETPLAN_IP_RULE_PRIO_UNSPEC);
YAML_UINT_DEFAULT(def, event, emitter, "type-of-service", r->tos, NETPLAN_IP_RULE_TOS_UNSPEC);
YAML_UINT_DEFAULT(def, event, emitter, "mark", r->fwmark, NETPLAN_IP_RULE_FW_MARK_UNSPEC);
YAML_STRING(def, event, emitter, "from", r->from);
YAML_STRING(def, event, emitter, "to", r->to);
YAML_MAPPING_CLOSE(event, emitter);
}
YAML_SEQUENCE_CLOSE(event, emitter);
}
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static gboolean
write_openvswitch(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanOVSSettings* ovs, NetplanBackend backend, GHashTable *ovs_ports)
{
GHashTableIter iter;
gpointer key, value;
if (has_openvswitch(ovs, backend, ovs_ports)) {
YAML_SCALAR_PLAIN(event, emitter, "openvswitch");
YAML_MAPPING_OPEN(event, emitter);
if (ovs_ports && g_hash_table_size(ovs_ports) > 0) {
YAML_SCALAR_PLAIN(event, emitter, "ports");
YAML_SEQUENCE_OPEN(event, emitter);
g_hash_table_iter_init(&iter, ovs_ports);
while (g_hash_table_iter_next (&iter, &key, &value)) {
YAML_SEQUENCE_OPEN(event, emitter);
YAML_SCALAR_PLAIN(event, emitter, key);
YAML_SCALAR_PLAIN(event, emitter, value);
YAML_SEQUENCE_CLOSE(event, emitter);
g_hash_table_iter_remove(&iter);
}
YAML_SEQUENCE_CLOSE(event, emitter);
}
if (ovs->external_ids && g_hash_table_size(ovs->external_ids) > 0) {
YAML_SCALAR_PLAIN(event, emitter, "external-ids");
YAML_MAPPING_OPEN(event, emitter);
g_hash_table_iter_init(&iter, ovs->external_ids);
while (g_hash_table_iter_next (&iter, &key, &value)) {
YAML_NONNULL_STRING(event, emitter, key, value);
}
YAML_MAPPING_CLOSE(event, emitter);
}
if (ovs->other_config && g_hash_table_size(ovs->other_config) > 0) {
YAML_SCALAR_PLAIN(event, emitter, "other-config");
YAML_MAPPING_OPEN(event, emitter);
g_hash_table_iter_init(&iter, ovs->other_config);
while (g_hash_table_iter_next (&iter, &key, &value)) {
YAML_NONNULL_STRING(event, emitter, key, value);
}
YAML_MAPPING_CLOSE(event, emitter);
}
YAML_NONNULL_STRING(event, emitter, "lacp", ovs->lacp);
YAML_NONNULL_STRING(event, emitter, "fail-mode", ovs->fail_mode);
if (ovs->mcast_snooping)
YAML_NONNULL_STRING_PLAIN(event, emitter, "mcast-snooping", "true");
if (ovs->rstp)
YAML_NONNULL_STRING_PLAIN(event, emitter, "rstp", "true");
if (ovs->protocols && ovs->protocols->len > 0) {
YAML_SCALAR_PLAIN(event, emitter, "protocols");
YAML_SEQUENCE_OPEN(event, emitter);
for (unsigned i = 0; i < ovs->protocols->len; ++i) {
const gchar *proto = g_array_index(ovs->protocols, gchar*, i);
YAML_SCALAR_PLAIN(event, emitter, proto);
}
YAML_SEQUENCE_CLOSE(event, emitter);
}
if (ovs->ssl.ca_certificate || ovs->ssl.client_certificate || ovs->ssl.client_key) {
YAML_SCALAR_PLAIN(event, emitter, "ssl");
YAML_MAPPING_OPEN(event, emitter);
YAML_NONNULL_STRING(event, emitter, "ca-cert", ovs->ssl.ca_certificate);
YAML_NONNULL_STRING(event, emitter, "certificate", ovs->ssl.client_certificate);
YAML_NONNULL_STRING(event, emitter, "private-key", ovs->ssl.client_key);
YAML_MAPPING_CLOSE(event, emitter);
}
if (ovs->controller.connection_mode || ovs->controller.addresses) {
YAML_SCALAR_PLAIN(event, emitter, "controller");
YAML_MAPPING_OPEN(event, emitter);
YAML_NONNULL_STRING(event, emitter, "connection-mode", ovs->controller.connection_mode);
if (ovs->controller.addresses) {
YAML_SCALAR_PLAIN(event, emitter, "addresses");
YAML_SEQUENCE_OPEN(event, emitter);
for (unsigned i = 0; i < ovs->controller.addresses->len; ++i) {
const gchar *addr = g_array_index(ovs->controller.addresses, gchar*, i);
YAML_SCALAR_QUOTED(event, emitter, addr);
}
YAML_SEQUENCE_CLOSE(event, emitter);
}
YAML_MAPPING_CLOSE(event, emitter);
}
YAML_MAPPING_CLOSE(event, emitter);
}
return TRUE;
err_path: return FALSE; // LCOV_EXCL_LINE
}
static void
_serialize_yaml(
const NetplanState* np_state,
yaml_event_t* event,
yaml_emitter_t* emitter,
const NetplanNetDefinition* def)
{
GArray* tmp_arr = NULL;
GHashTableIter iter;
gpointer key, value;
YAML_SCALAR_PLAIN(event, emitter, def->id);
YAML_MAPPING_OPEN(event, emitter);
/* We write out the renderer in very specific circumstances. There's a special case for VLANs,
* and unless explicitly specified, we only write out standard renderers if they don't match the global
* one or are the default and the global one isn't specified. */
if (def->type == NETPLAN_DEF_TYPE_VLAN && def->sriov_vlan_filter) {
YAML_NONNULL_STRING_PLAIN(event, emitter, "renderer", "sriov");
} else if (DIRTY(def, def->backend) ||
( def->backend != get_default_backend_for_type(np_state->backend, def->type)
&& def->backend != np_state->backend
&& def->backend != NETPLAN_BACKEND_OVS)) {
YAML_NONNULL_STRING_PLAIN(event, emitter, "renderer", netplan_backend_name(def->backend));
}
if (def->has_match)
write_match(event, emitter, def);
/* Do not try to handle "unknown" connection types (full fallback/passthrough) */
if (def->type == NETPLAN_DEF_TYPE_NM)
goto only_passthrough;
if (def->optional)
YAML_NONNULL_STRING_PLAIN(event, emitter, "optional", "true");
if (def->critical)
YAML_NONNULL_STRING_PLAIN(event, emitter, "critical", "true");
if (def->ignore_carrier)
YAML_NONNULL_STRING_PLAIN(event, emitter, "ignore-carrier", "true");
if (def->ip4_addresses || def->ip6_addresses || def->address_options)
write_addresses(event, emitter, def);
if (def->ip4_nameservers || def->ip6_nameservers || def->search_domains)
write_nameservers(event, emitter, def);
YAML_STRING_PLAIN(def, event, emitter, "gateway4", def->gateway4);
YAML_STRING_PLAIN(def, event, emitter, "gateway6", def->gateway6);
YAML_STRING(def, event, emitter, "dhcp-identifier", def->dhcp_identifier);
YAML_BOOL_TRUE(def, event, emitter, "dhcp4", def->dhcp4);
write_dhcp_overrides(event, emitter, "dhcp4-overrides", def, &def->dhcp4_overrides);
YAML_BOOL_TRUE(def, event, emitter, "dhcp6", def->dhcp6);
write_dhcp_overrides(event, emitter, "dhcp6-overrides", def, &def->dhcp6_overrides);
if (def->accept_ra == NETPLAN_RA_MODE_ENABLED) {
YAML_NONNULL_STRING_PLAIN(event, emitter, "accept-ra", "true");
} else if (def->accept_ra == NETPLAN_RA_MODE_DISABLED) {
YAML_NONNULL_STRING_PLAIN(event, emitter, "accept-ra", "false");
}
YAML_STRING(def, event, emitter, "macaddress", def->set_mac);
YAML_STRING(def, event, emitter, "set-name", def->set_name);
YAML_NONNULL_STRING(event, emitter, "ipv6-address-generation", netplan_addr_gen_mode_name(def->ip6_addr_gen_mode));
YAML_STRING(def, event, emitter, "ipv6-address-token", def->ip6_addr_gen_token);
YAML_BOOL_TRUE(def, event, emitter, "ipv6-privacy", def->ip6_privacy);
YAML_UINT_0(def, event, emitter, "ipv6-mtu", def->ipv6_mtubytes);
YAML_UINT_0(def, event, emitter, "mtu", def->mtubytes);
if (def->emit_lldp)
YAML_NONNULL_STRING_PLAIN(event, emitter, "emit-lldp", "true");
if (def->has_auth)
write_auth(event, emitter, def->auth);
/* activation-mode */
YAML_STRING(def, event, emitter, "activation-mode", def->activation_mode);
/* SR-IOV */
if (def->sriov_link)
YAML_STRING(def, event, emitter, "link", def->sriov_link->id);
YAML_UINT_DEFAULT(def, event, emitter, "virtual-function-count", def->sriov_explicit_vf_count, G_MAXUINT);
YAML_STRING(def, event, emitter, "embedded-switch-mode", def->embedded_switch_mode);
YAML_BOOL_TRUE(def, event, emitter, "delay-virtual-functions-rebind",
def->sriov_delay_virtual_functions_rebind);
if (def->type == NETPLAN_DEF_TYPE_VETH && def->veth_peer_link)
YAML_STRING(def, event, emitter, "peer", def->veth_peer_link->id);
/* Search interfaces */
if (def->type == NETPLAN_DEF_TYPE_BRIDGE || def->type == NETPLAN_DEF_TYPE_BOND || def->type == NETPLAN_DEF_TYPE_VRF) {
tmp_arr = g_array_new(FALSE, FALSE, sizeof(NetplanNetDefinition*));
g_hash_table_iter_init(&iter, np_state->netdefs);
while (g_hash_table_iter_next (&iter, &key, &value)) {
NetplanNetDefinition *nd = (NetplanNetDefinition *) value;
if (g_strcmp0(nd->bond, def->id) == 0 || g_strcmp0(nd->bridge, def->id) == 0 || nd->vrf_link == def)
g_array_append_val(tmp_arr, nd);
}
if (tmp_arr->len > 0) {
YAML_SCALAR_PLAIN(event, emitter, "interfaces");
YAML_SEQUENCE_OPEN(event, emitter);
for (unsigned i = 0; i < tmp_arr->len; ++i) {
NetplanNetDefinition *nd = g_array_index(tmp_arr, NetplanNetDefinition*, i);
YAML_SCALAR_PLAIN(event, emitter, nd->id);
}
YAML_SEQUENCE_CLOSE(event, emitter);
}
write_bond_params(event, emitter, def);
write_bridge_params(event, emitter, def, tmp_arr);
g_array_free(tmp_arr, TRUE);
}
write_routes(event, emitter, def);
YAML_BOOL_TRISTATE(def, event, emitter, "neigh-suppress", def->bridge_neigh_suppress);
/* VLAN settings */
if (def->type == NETPLAN_DEF_TYPE_VLAN) {
YAML_UINT_DEFAULT(def, event, emitter, "id", def->vlan_id, G_MAXUINT);
if (def->vlan_link)
YAML_STRING(def, event, emitter, "link", def->vlan_link->id);
}
/* VRF settings */
if (def->type == NETPLAN_DEF_TYPE_VRF)
YAML_UINT_DEFAULT(def, event, emitter, "table", def->vrf_table, G_MAXUINT);
/* Tunnel settings */
if (def->type == NETPLAN_DEF_TYPE_TUNNEL) {
write_tunnel_settings(event, emitter, def);
}
/* wake-on-lan */
YAML_BOOL_TRUE(def, event, emitter, "wakeonlan", def->wake_on_lan);
/* Offload options */
if (def->receive_checksum_offload != NETPLAN_TRISTATE_UNSET)
YAML_BOOL_TRUE(def, event, emitter, "receive-checksum-offload", def->receive_checksum_offload);
if (def->transmit_checksum_offload != NETPLAN_TRISTATE_UNSET)
YAML_BOOL_TRUE(def, event, emitter, "transmit-checksum-offload", def->transmit_checksum_offload);
if (def->tcp_segmentation_offload != NETPLAN_TRISTATE_UNSET)
YAML_BOOL_TRUE(def, event, emitter, "tcp-segmentation-offload", def->tcp_segmentation_offload);
if (def->tcp6_segmentation_offload != NETPLAN_TRISTATE_UNSET)
YAML_BOOL_TRUE(def, event, emitter, "tcp6-segmentation-offload", def->tcp6_segmentation_offload);
if (def->generic_segmentation_offload != NETPLAN_TRISTATE_UNSET)
YAML_BOOL_TRUE(def, event, emitter, "generic-segmentation-offload", def->generic_segmentation_offload);
if (def->generic_receive_offload != NETPLAN_TRISTATE_UNSET)
YAML_BOOL_TRUE(def, event, emitter, "generic-receive-offload", def->generic_receive_offload);
if (def->large_receive_offload != NETPLAN_TRISTATE_UNSET)
YAML_BOOL_TRUE(def, event, emitter, "large-receive-offload", def->large_receive_offload);
if (def->wowlan && def->wowlan != NETPLAN_WIFI_WOWLAN_DEFAULT) {
YAML_SCALAR_PLAIN(event, emitter, "wakeonwlan");
YAML_SEQUENCE_OPEN(event, emitter);
/* XXX: make sure to extend if NetplanWifiWowlanFlag is extended */
if (def->wowlan & NETPLAN_WIFI_WOWLAN_ANY)
YAML_SCALAR_PLAIN(event, emitter, "any");
if (def->wowlan & NETPLAN_WIFI_WOWLAN_DISCONNECT)
YAML_SCALAR_PLAIN(event, emitter, "disconnect");
if (def->wowlan & NETPLAN_WIFI_WOWLAN_MAGIC)
YAML_SCALAR_PLAIN(event, emitter, "magic_pkt");
if (def->wowlan & NETPLAN_WIFI_WOWLAN_GTK_REKEY_FAILURE)
YAML_SCALAR_PLAIN(event, emitter, "gtk_rekey_failure");
if (def->wowlan & NETPLAN_WIFI_WOWLAN_EAP_IDENTITY_REQ)
YAML_SCALAR_PLAIN(event, emitter, "eap_identity_req");
if (def->wowlan & NETPLAN_WIFI_WOWLAN_4WAY_HANDSHAKE)
YAML_SCALAR_PLAIN(event, emitter, "four_way_handshake");
if (def->wowlan & NETPLAN_WIFI_WOWLAN_RFKILL_RELEASE)
YAML_SCALAR_PLAIN(event, emitter, "rfkill_release");
if (def->wowlan & NETPLAN_WIFI_WOWLAN_TCP)
YAML_SCALAR_PLAIN(event, emitter, "tcp");
YAML_SEQUENCE_CLOSE(event, emitter);
}
YAML_STRING(def, event, emitter, "regulatory-domain", def->regulatory_domain);
if (def->optional_addresses) {
YAML_SCALAR_PLAIN(event, emitter, "optional-addresses");
YAML_SEQUENCE_OPEN(event, emitter);
if (def->optional_addresses & NETPLAN_OPTIONAL_IPV4_LL)
YAML_SCALAR_PLAIN(event, emitter, "ipv4-ll")
if (def->optional_addresses & NETPLAN_OPTIONAL_IPV6_RA)
YAML_SCALAR_PLAIN(event, emitter, "ipv6-ra")
if (def->optional_addresses & NETPLAN_OPTIONAL_DHCP4)
YAML_SCALAR_PLAIN(event, emitter, "dhcp4")
if (def->optional_addresses & NETPLAN_OPTIONAL_DHCP6)
YAML_SCALAR_PLAIN(event, emitter, "dhcp6")
if (def->optional_addresses & NETPLAN_OPTIONAL_STATIC)
YAML_SCALAR_PLAIN(event, emitter, "static")
YAML_SEQUENCE_CLOSE(event, emitter);
}
/* Generate "link-local" if it differs from the default: "[ ipv6 ]" */
if (!(def->linklocal.ipv6 && !def->linklocal.ipv4)) {
YAML_SCALAR_PLAIN(event, emitter, "link-local");
YAML_SEQUENCE_OPEN(event, emitter);
if (def->linklocal.ipv4)
YAML_SCALAR_PLAIN(event, emitter, "ipv4");
if (def->linklocal.ipv6)
YAML_SCALAR_PLAIN(event, emitter, "ipv6");
YAML_SEQUENCE_CLOSE(event, emitter);
}
write_openvswitch(event, emitter, &def->ovs_settings, def->backend, NULL);
/* InfiniBand */
if (def->ib_mode != NETPLAN_IB_MODE_KERNEL) {
const char* ib_mode_str = netplan_infiniband_mode_name(def->ib_mode);
YAML_STRING(def, event, emitter, "infiniband-mode", ib_mode_str);
}
if (def->type == NETPLAN_DEF_TYPE_MODEM)
write_modem_params(event, emitter, def);
if (def->type == NETPLAN_DEF_TYPE_WIFI)
if (!write_access_points(event, emitter, def)) goto err_path;
/* Handle devices in full fallback/passthrough mode (i.e. 'nm-devices') */
only_passthrough:
if (!write_backend_settings(event, emitter, def->backend_settings)) goto err_path;
/* Close remaining mappings */
YAML_MAPPING_CLOSE(event, emitter);
return;
// LCOV_EXCL_START
err_path:
g_warning("Error generating YAML: %s", emitter->problem);
return;
// LCOV_EXCL_STOP
}
/**
* Generate the Netplan YAML configuration for the selected netdef
* @np_state: NetplanState (as pointer), the global state to which the netdef belongs
* @def: NetplanNetDefinition (as pointer), the data to be serialized
* @rootdir: If not %NULL, generate configuration in this root directory
* (useful for testing).
*/
gboolean
netplan_netdef_write_yaml(
const NetplanState* np_state,
const NetplanNetDefinition* netdef,
const char* rootdir,
GError** error)
{
g_autofree gchar *filename = NULL;
g_autofree gchar *path = NULL;
mode_t orig_umask;
/* NetworkManager produces one file per connection profile
* It's 90-* to be higher priority than the default 70-netplan-set.yaml */
if (netdef->backend_settings.uuid)
filename = g_strconcat("90-NM-", netdef->backend_settings.uuid, ".yaml", NULL);
else
filename = g_strconcat("10-netplan-", netdef->id, ".yaml", NULL);
path = g_build_path(G_DIR_SEPARATOR_S, rootdir ?: G_DIR_SEPARATOR_S, "etc", "netplan", filename, NULL);
/* Start rendering YAML output */
yaml_emitter_t emitter_data;
yaml_event_t event_data;
yaml_emitter_t* emitter = &emitter_data;
yaml_event_t* event = &event_data;
orig_umask = umask(077); // owner (root) read-only
FILE *output = fopen(path, "wb");
umask(orig_umask);
YAML_OUT_START(event, emitter, output);
/* build the netplan boilerplate YAML structure */
YAML_SCALAR_PLAIN(event, emitter, "network");
YAML_MAPPING_OPEN(event, emitter);
YAML_NONNULL_STRING_PLAIN(event, emitter, "version", "2");
if (netplan_def_type_name(netdef->type)) {
YAML_SCALAR_PLAIN(event, emitter, netplan_def_type_name(netdef->type));
YAML_MAPPING_OPEN(event, emitter);
_serialize_yaml(np_state, event, emitter, netdef);
YAML_MAPPING_CLOSE(event, emitter);
}
/* Close remaining mappings */
YAML_MAPPING_CLOSE(event, emitter);
/* Tear down the YAML emitter */
YAML_OUT_STOP(event, emitter);
fclose(output);
return TRUE;
// LCOV_EXCL_START
err_path:
g_set_error(error, NETPLAN_EMITTER_ERROR, NETPLAN_ERROR_YAML_EMITTER, "Error generating YAML: %s", emitter->problem);
yaml_emitter_delete(emitter);
fclose(output);
return FALSE;
// LCOV_EXCL_STOP
}
static int
contains_netdef_type(gconstpointer value, gconstpointer user_data)
{
const NetplanNetDefinition *nd = value;
const NetplanDefType *type = user_data;
return nd->type == *type ? 0 : -1;
}
static gboolean
netplan_netdef_list_write_yaml(const NetplanState* np_state, GList* netdefs, int out_fd, const char* out_fname, gboolean is_fallback, GError** error)
{
GHashTable *ovs_ports = NULL;
int dup_fd = dup(out_fd);
if (dup_fd < 0)
goto file_error; // LCOV_EXCL_LINE
FILE* out_stream = fdopen(dup_fd, "w");
if (!out_stream)
goto file_error;
/* Start rendering YAML output */
yaml_emitter_t emitter_data;
yaml_event_t event_data;
yaml_emitter_t* emitter = &emitter_data;
yaml_event_t* event = &event_data;
YAML_OUT_START(event, emitter, out_stream);
/* build the netplan boilerplate YAML structure */
YAML_SCALAR_PLAIN(event, emitter, "network");
YAML_MAPPING_OPEN(event, emitter);
/* We support version 2 only, currently */
YAML_NONNULL_STRING_PLAIN(event, emitter, "version", "2");
/* fallback to default global handling, if renderer was not set for this file */
NetplanBackend renderer = netplan_state_get_backend(np_state);
/* Try to find a file specific (global) renderer.
* If this is the fallback file (70-netplan-set.yaml or .yaml),
* a renderer parsed from a YAML patch takes precedence. */
if (out_fname && np_state->global_renderer) {
gpointer value;
renderer = GPOINTER_TO_INT(g_hash_table_lookup(np_state->global_renderer, out_fname));
/* A renderer parsed from an (anonymous) YAML patch takes precendence
* (e.g. "netplan set ..."). Such data does not have any filename
* associated to it in the global_renderer map (i.e. empty string). */
if (is_fallback && g_hash_table_lookup_extended(np_state->global_renderer, "", NULL, &value))
renderer = GPOINTER_TO_INT(value);
}
if (renderer == NETPLAN_BACKEND_NM || renderer == NETPLAN_BACKEND_NETWORKD)
YAML_NONNULL_STRING_PLAIN(event, emitter, "renderer", netplan_backend_name(renderer));
/* Do not write any netdefs, if we're just setting/updating some globals,
* e.g.: netplan set "network.renderer=NetworkManager" */
if (!netdefs)
goto skip_netdefs;
/* Go through the netdefs type-by-type */
for (unsigned i = 0; i < NETPLAN_DEF_TYPE_MAX_; ++i) {
if (i == NETPLAN_DEF_TYPE_NM_PLACEHOLDER_) continue;
/* Per-netdef config */
if (g_list_find_custom(netdefs, &i, contains_netdef_type)) {
if (i == NETPLAN_DEF_TYPE_PORT) {
GList* iter = netdefs;
while (iter) {
NetplanNetDefinition *def = iter->data;
if (def->type == i) {
if (!ovs_ports)
ovs_ports = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
/* Check that the peer hasn't already been inserted to avoid duplication */
if (!g_hash_table_lookup(ovs_ports, def->peer))
g_hash_table_insert(ovs_ports, g_strdup(def->id), g_strdup(def->peer));
}
iter = g_list_next(iter);
}
} else if (netplan_def_type_name(i)) {
GList* iter = netdefs;
YAML_SCALAR_PLAIN(event, emitter, netplan_def_type_name(i));
YAML_MAPPING_OPEN(event, emitter);
while (iter) {
NetplanNetDefinition *def = iter->data;
if (def->type == i)
_serialize_yaml(np_state, event, emitter, def);
iter = g_list_next(iter);
}
YAML_MAPPING_CLOSE(event, emitter);
}
}
}
skip_netdefs:
write_openvswitch(event, emitter, &np_state->ovs_settings, NETPLAN_BACKEND_NONE, ovs_ports);
/* Close remaining mappings */
YAML_MAPPING_CLOSE(event, emitter);
/* Tear down the YAML emitter */
YAML_OUT_STOP(event, emitter);
fclose(out_stream);
return TRUE;
// LCOV_EXCL_START
err_path:
g_set_error(error, NETPLAN_EMITTER_ERROR, NETPLAN_ERROR_YAML_EMITTER, "Error generating YAML: %s", emitter->problem);
yaml_emitter_delete(emitter);
fclose(out_stream);
return FALSE;
// LCOV_EXCL_STOP
file_error:
g_set_error(error, NETPLAN_FILE_ERROR, errno, "%m");
return FALSE;
}
/**
* Generate the YAML configuration, filtered to the data relevant to a particular file.
* Any data that's assigned to another file is ignored. Data that is not assigned is considered
* relevant.
*
* @np_state: the state for which to generate the config
* @filename: Relevant file basename (e.g. origin-hint.yaml)
* @rootdir: If not %NULL, generate configuration in this root directory
* (useful for testing).
*/
gboolean
netplan_state_write_yaml_file(const NetplanState* np_state, const char* filename, const char* rootdir, GError** error)
{
GList* iter = np_state->netdefs_ordered;
g_autofree gchar* path = NULL;
g_autofree gchar* tmp_path = NULL;
GList* to_write = NULL;
int out_fd;
path = g_build_path(G_DIR_SEPARATOR_S, rootdir ?: G_DIR_SEPARATOR_S, "etc", "netplan", filename, NULL);
while (iter) {
NetplanNetDefinition* netdef = iter->data;
const char* fname = netdef->filepath ? netdef->filepath : path;
if (g_strcmp0(fname, path) == 0)
to_write = g_list_append(to_write, netdef);
iter = iter->next;
}
/* Remove any existing file if there is no data to write */
gboolean write_globals = !!np_state->global_renderer;
if (to_write == NULL && !write_globals) {
if (unlink(path) && errno != ENOENT) {
g_set_error(error, NETPLAN_FILE_ERROR, errno, "%m");
return FALSE;
}
return TRUE;
}
tmp_path = g_strdup_printf("%s.XXXXXX", path);
/*
* glibc will create a file with mode 600 by default.
* Although, mkstemp manpage says that the POSIX spec doesn't say anything
* about file modes and the application should make sure umask is set
* appropriately before calling mkstemp().
*/
mode_t old_umask = umask(077);
out_fd = mkstemp(tmp_path); // permissions 0600 by default
umask(old_umask);
if (out_fd < 0) {
g_set_error(error, NETPLAN_FILE_ERROR, errno, "%m");
return FALSE;
}
gboolean ret = netplan_netdef_list_write_yaml(np_state, to_write, out_fd, path, TRUE, error);
g_list_free(to_write);
close(out_fd);
if (ret) {
if (rename(tmp_path, path) == 0)
return TRUE;
g_set_error(error, NETPLAN_FILE_ERROR, errno, "%m");
}
/* Something went wrong, clean up the tempfile! */
unlink(tmp_path);
return FALSE;
}
/**
* Dump the whole state into a single YAML file.
*
* @np_state: the state for which to generate the config
* @out_fd: File descriptor to an opened file into which to dump the content
*/
gboolean
netplan_state_dump_yaml(const NetplanState* np_state, int out_fd, GError** error)
{
if (!np_state->netdefs_ordered && !netplan_state_has_nondefault_globals(np_state))
return TRUE;
return netplan_netdef_list_write_yaml(np_state, np_state->netdefs_ordered, out_fd, NULL, TRUE, error);
}
/**
* Regenerate the YAML configuration files from a given state. Any state that
* hasn't an associated filepath will use the default_filename output in the
* standard config directory.
*
* @np_state: the state for which to generate the config
* @default_filename: Default config file, cannot be NULL or empty
* @rootdir: If not %NULL, generate configuration in this root directory
* (useful for testing).
*/
gboolean
netplan_state_update_yaml_hierarchy(const NetplanState* np_state, const char* default_filename, const char* rootdir, GError** error)
{
g_autofree gchar *default_path = NULL;
gboolean ret = FALSE;
GHashTableIter hash_iter;
gpointer key, value;
GHashTable *perfile_netdefs;
g_assert(default_filename != NULL && *default_filename != '\0');
perfile_netdefs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_list_free);
default_path = g_build_path(G_DIR_SEPARATOR_S, rootdir ?: G_DIR_SEPARATOR_S, "etc", "netplan", default_filename, NULL);
int out_fd = -1;
/* Dump global conf to the default path */
if (!np_state->netdefs || g_hash_table_size(np_state->netdefs) == 0) {
if ( has_openvswitch(&np_state->ovs_settings, NETPLAN_BACKEND_NONE, NULL)
|| (np_state->backend != NETPLAN_BACKEND_NONE && np_state->global_renderer &&
( g_hash_table_contains(np_state->global_renderer, default_path) // 70-netplan-set.yaml already exsits and defines a global renderer
|| g_hash_table_contains(np_state->global_renderer, "")))) { // 70-netplan-set.yaml doesn't exist, but we need to create it to define a global renderer
g_hash_table_insert(perfile_netdefs, default_path, NULL);
}
} else {
GList* iter = np_state->netdefs_ordered;
while (iter) {
NetplanNetDefinition* netdef = iter->data;
const char* filename = netdef->filepath ? netdef->filepath : default_path;
GList* list = NULL;
g_hash_table_steal_extended(perfile_netdefs, filename, NULL, (gpointer*)&list);
g_hash_table_insert(perfile_netdefs, (gpointer)filename, g_list_append(list, netdef));
iter = iter->next;
}
}
/* Add files containing a global renderer value to "perfile_netdefs", so
* they are updated on disk. */
if (np_state->global_renderer && g_hash_table_size(np_state->global_renderer) > 0) {
g_hash_table_iter_init(&hash_iter, np_state->global_renderer);
while (g_hash_table_iter_next (&hash_iter, &key, &value)) {
char *filename = key;
/* Anonymous globals will go to the default YAML (see above) */
if (g_strcmp0(filename, "") == 0)
continue;
/* Ignore the update of this file if it's already going to be
* written, caused by updated netdefs. */
if (!g_hash_table_contains(perfile_netdefs, filename))
g_hash_table_insert(perfile_netdefs, filename, NULL);
}
}
g_hash_table_iter_init(&hash_iter, perfile_netdefs);
while (g_hash_table_iter_next (&hash_iter, &key, &value)) {
const char *filename = key;
gboolean is_fallback = (g_strcmp0(filename, default_path) == 0);
GList* netdefs = value;
out_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (out_fd < 0)
goto file_error;
if (!netplan_netdef_list_write_yaml(np_state, netdefs, out_fd, filename, is_fallback, error))
goto cleanup; // LCOV_EXCL_LINE
close(out_fd);
out_fd = -1;
}
/* Remove any referenced source file that doesn't have any associated data.
Presumably, it is data that has been obsoleted by files loaded
afterwards, typically via `netplan set`. */
if (np_state->sources) {
g_hash_table_iter_init(&hash_iter, np_state->sources);
while (g_hash_table_iter_next (&hash_iter, &key, &value)) {
if (!g_hash_table_contains(perfile_netdefs, key)) {
if (unlink(key) && errno != ENOENT)
goto file_error; // LCOV_EXCL_LINE
}
}
}
ret = TRUE;
goto cleanup;
file_error:
g_set_error(error, NETPLAN_FILE_ERROR, errno, "%m");
ret = FALSE;
cleanup:
if (out_fd >= 0)
close(out_fd); // LCOV_EXCL_LINE
g_hash_table_destroy(perfile_netdefs);
return ret;
}
netplan-0.107.1/src/netplan.script 0000775 0000000 0000000 00000001442 14536110317 0017004 0 ustar 00root root 0000000 0000000 #!/usr/bin/python3
#
# Copyright (C) 2018 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# 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 .
'''netplan command line'''
from netplan_cli import Netplan
netplan = Netplan()
netplan.main()
netplan-0.107.1/src/networkd.c 0000664 0000000 0000000 00000161716 14536110317 0016126 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2016 Canonical, Ltd.
* Author: Martin Pitt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "networkd.h"
#include "parse.h"
#include "parse-globals.h"
#include "names.h"
#include "util.h"
#include "util-internal.h"
#include "validation.h"
/**
* Append WiFi frequencies to wpa_supplicant's freq_list=
*/
static void
wifi_append_freq(__unused gpointer key, gpointer value, gpointer user_data)
{
GString* s = user_data;
g_string_append_printf(s, "%d ", GPOINTER_TO_INT(value));
}
/**
* append wowlan_triggers= string for wpa_supplicant.conf
*/
static gboolean
append_wifi_wowlan_flags(NetplanWifiWowlanFlag flag, GString* str, GError** error) {
if (flag & NETPLAN_WIFI_WOWLAN_TYPES[0].flag || flag >= NETPLAN_WIFI_WOWLAN_TCP) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: unsupported wowlan_triggers mask: 0x%x\n", flag);
return FALSE;
}
for (unsigned i = 0; NETPLAN_WIFI_WOWLAN_TYPES[i].name != NULL; ++i) {
if (flag & NETPLAN_WIFI_WOWLAN_TYPES[i].flag) {
g_string_append_printf(str, "%s ", NETPLAN_WIFI_WOWLAN_TYPES[i].name);
}
}
/* replace trailing space with newline */
str = g_string_overwrite(str, str->len-1, "\n");
return TRUE;
}
/**
* Append [Match] section of @def to @s.
*/
static void
append_match_section(const NetplanNetDefinition* def, GString* s, gboolean match_rename)
{
/* Note: an empty [Match] section is interpreted as matching all devices,
* which is what we want for the simple case that you only have one device
* (of the given type) */
g_string_append(s, "[Match]\n");
if (def->match.driver && strchr(def->match.driver, '\t')) {
gchar **split = g_strsplit(def->match.driver, "\t", 0);
g_string_append_printf(s, "Driver=%s", split[0]);
for (unsigned i = 1; split[i]; ++i)
g_string_append_printf(s, " %s", split[i]);
g_string_append(s, "\n");
g_strfreev(split);
} else if (def->match.driver)
g_string_append_printf(s, "Driver=%s\n", def->match.driver);
if (def->match.mac) {
/* LP: #1804861 and LP: #1888726:
* Using bond, bridge, and VLAN devices results in sharing MAC
* addresses across interfaces. Match by PermanentMACAddress to match
* only the real phy interface and to continue to match it even after
* its MAC address has been changed.
*/
g_string_append_printf(s, "PermanentMACAddress=%s\n", def->match.mac);
}
/* name matching is special: if the .link renames the interface, the
* .network has to use the renamed one, otherwise the original one */
if (!match_rename && def->match.original_name)
g_string_append_printf(s, "OriginalName=%s\n", def->match.original_name);
if (match_rename) {
if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL)
g_string_append_printf(s, "Name=%s\n", def->id);
else if (def->set_name)
g_string_append_printf(s, "Name=%s\n", def->set_name);
else if (def->match.original_name)
g_string_append_printf(s, "Name=%s\n", def->match.original_name);
}
}
static void
write_bridge_params_networkd(GString* s, const NetplanNetDefinition* def)
{
GString *params = NULL;
if (def->custom_bridging) {
params = g_string_sized_new(200);
if (def->bridge_params.ageing_time)
g_string_append_printf(params, "AgeingTimeSec=%s\n", def->bridge_params.ageing_time);
if (def->bridge_params.priority)
g_string_append_printf(params, "Priority=%u\n", def->bridge_params.priority);
if (def->bridge_params.forward_delay)
g_string_append_printf(params, "ForwardDelaySec=%s\n", def->bridge_params.forward_delay);
if (def->bridge_params.hello_time)
g_string_append_printf(params, "HelloTimeSec=%s\n", def->bridge_params.hello_time);
if (def->bridge_params.max_age)
g_string_append_printf(params, "MaxAgeSec=%s\n", def->bridge_params.max_age);
g_string_append_printf(params, "STP=%s\n", def->bridge_params.stp ? "true" : "false");
g_string_append_printf(s, "\n[Bridge]\n%s", params->str);
g_string_free(params, TRUE);
}
}
static void
write_tunnel_params(GString* s, const NetplanNetDefinition* def)
{
GString *params = NULL;
params = g_string_sized_new(200);
g_string_printf(params, "Independent=true\n");
if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_IPIP6 || def->tunnel.mode == NETPLAN_TUNNEL_MODE_IP6IP6)
g_string_append_printf(params, "Mode=%s\n", netplan_tunnel_mode_name(def->tunnel.mode));
if (def->tunnel.local_ip)
g_string_append_printf(params, "Local=%s\n", def->tunnel.local_ip);
g_string_append_printf(params, "Remote=%s\n", def->tunnel.remote_ip);
if (def->tunnel_ttl)
g_string_append_printf(params, "TTL=%u\n", def->tunnel_ttl);
if (def->tunnel.input_key)
g_string_append_printf(params, "InputKey=%s\n", def->tunnel.input_key);
if (def->tunnel.output_key)
g_string_append_printf(params, "OutputKey=%s\n", def->tunnel.output_key);
g_string_append_printf(s, "\n[Tunnel]\n%s", params->str);
g_string_free(params, TRUE);
}
static void
write_wireguard_params(GString* s, const NetplanNetDefinition* def)
{
GString *params = NULL;
params = g_string_sized_new(200);
g_assert(def->tunnel.private_key);
/* The "PrivateKeyFile=" setting is available as of systemd-netwokrd v242+
* Base64 encoded PrivateKey= or absolute PrivateKeyFile= fields are mandatory.
*
* The key was already validated via validate_tunnel_grammar(), but we need
* to differentiate between base64 key VS absolute path key-file. And a base64
* string could (theoretically) start with '/', so we use is_wireguard_key()
* as well to check for more specific characteristics (if needed). */
if (def->tunnel.private_key[0] == '/' && !is_wireguard_key(def->tunnel.private_key))
g_string_append_printf(params, "PrivateKeyFile=%s\n", def->tunnel.private_key);
else
g_string_append_printf(params, "PrivateKey=%s\n", def->tunnel.private_key);
if (def->tunnel.port)
g_string_append_printf(params, "ListenPort=%u\n", def->tunnel.port);
/* This is called FirewallMark= as of systemd v243, but we keep calling it FwMark= for
backwards compatibility. FwMark= is still supported, but deprecated:
https://github.com/systemd/systemd/pull/12478 */
if (def->tunnel.fwmark)
g_string_append_printf(params, "FwMark=%u\n", def->tunnel.fwmark);
g_string_append_printf(s, "\n[WireGuard]\n%s", params->str);
g_string_free(params, TRUE);
for (guint i = 0; i < def->wireguard_peers->len; i++) {
NetplanWireguardPeer *peer = g_array_index (def->wireguard_peers, NetplanWireguardPeer*, i);
GString *peer_s = g_string_sized_new(200);
g_string_append_printf(peer_s, "PublicKey=%s\n", peer->public_key);
g_string_append(peer_s, "AllowedIPs=");
for (guint i = 0; i < peer->allowed_ips->len; ++i) {
if (i > 0 )
g_string_append_c(peer_s, ',');
g_string_append_printf(peer_s, "%s", g_array_index(peer->allowed_ips, char*, i));
}
g_string_append_c(peer_s, '\n');
if (peer->keepalive)
g_string_append_printf(peer_s, "PersistentKeepalive=%d\n", peer->keepalive);
if (peer->endpoint)
g_string_append_printf(peer_s, "Endpoint=%s\n", peer->endpoint);
/* The key was already validated via validate_tunnel_grammar(), but we need
* to differentiate between base64 key VS absolute path key-file. And a base64
* string could (theoretically) start with '/', so we use is_wireguard_key()
* as well to check for more specific characteristics (if needed). */
if (peer->preshared_key) {
if (peer->preshared_key[0] == '/' && !is_wireguard_key(peer->preshared_key))
g_string_append_printf(peer_s, "PresharedKeyFile=%s\n", peer->preshared_key);
else
g_string_append_printf(peer_s, "PresharedKey=%s\n", peer->preshared_key);
}
g_string_append_printf(s, "\n[WireGuardPeer]\n%s", peer_s->str);
g_string_free(peer_s, TRUE);
}
}
static void
write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char* path)
{
GString* s = NULL;
mode_t orig_umask;
/* Don't write .link files for virtual devices; they use .netdev instead.
* Don't write .link files for MODEM devices, as they aren't supported by networkd.
*/
if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL || def->type == NETPLAN_DEF_TYPE_MODEM)
return;
/* do we need to write a .link file? */
if (!def->set_name &&
!def->wake_on_lan &&
!def->mtubytes &&
(def->receive_checksum_offload == NETPLAN_TRISTATE_UNSET) &&
(def->transmit_checksum_offload == NETPLAN_TRISTATE_UNSET) &&
(def->tcp_segmentation_offload == NETPLAN_TRISTATE_UNSET) &&
(def->tcp6_segmentation_offload == NETPLAN_TRISTATE_UNSET) &&
(def->generic_segmentation_offload == NETPLAN_TRISTATE_UNSET) &&
(def->generic_receive_offload == NETPLAN_TRISTATE_UNSET) &&
(def->large_receive_offload == NETPLAN_TRISTATE_UNSET))
return;
/* build file contents */
s = g_string_sized_new(200);
append_match_section(def, s, FALSE);
g_string_append(s, "\n[Link]\n");
if (def->set_name)
g_string_append_printf(s, "Name=%s\n", def->set_name);
/* FIXME: Should this be turned from bool to str and support multiple values? */
g_string_append_printf(s, "WakeOnLan=%s\n", def->wake_on_lan ? "magic" : "off");
if (def->mtubytes)
g_string_append_printf(s, "MTUBytes=%u\n", def->mtubytes);
/* Offload options */
if (def->receive_checksum_offload != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(s, "ReceiveChecksumOffload=%s\n",
(def->receive_checksum_offload ? "true" : "false"));
if (def->transmit_checksum_offload != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(s, "TransmitChecksumOffload=%s\n",
(def->transmit_checksum_offload ? "true" : "false"));
if (def->tcp_segmentation_offload != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(s, "TCPSegmentationOffload=%s\n",
(def->tcp_segmentation_offload ? "true" : "false"));
if (def->tcp6_segmentation_offload != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(s, "TCP6SegmentationOffload=%s\n",
(def->tcp6_segmentation_offload ? "true" : "false"));
if (def->generic_segmentation_offload != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(s, "GenericSegmentationOffload=%s\n",
(def->generic_segmentation_offload ? "true" : "false"));
if (def->generic_receive_offload != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(s, "GenericReceiveOffload=%s\n",
(def->generic_receive_offload ? "true" : "false"));
if (def->large_receive_offload != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(s, "LargeReceiveOffload=%s\n",
(def->large_receive_offload ? "true" : "false"));
orig_umask = umask(022);
g_string_free_to_file(s, rootdir, path, ".link");
umask(orig_umask);
}
static gboolean
write_regdom(const NetplanNetDefinition* def, const char* rootdir, GError** error)
{
g_assert(def->regulatory_domain);
g_autofree gchar* id_escaped = NULL;
g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/network.target.wants/netplan-regdom.service", NULL);
g_autofree char* path = g_strjoin(NULL, "/run/systemd/system/netplan-regdom.service", NULL);
GString* s = g_string_new("[Unit]\n");
g_string_append(s, "Description=Netplan regulatory-domain configuration\n");
g_string_append(s, "After=network.target\n");
g_string_append(s, "ConditionFileIsExecutable="SBINDIR"/iw\n");
g_string_append(s, "\n[Service]\nType=oneshot\n");
g_string_append_printf(s, "ExecStart="SBINDIR"/iw reg set %s\n", def->regulatory_domain);
g_string_free_to_file(s, rootdir, path, NULL);
safe_mkdir_p_dir(link);
if (symlink(path, link) < 0 && errno != EEXIST) {
// LCOV_EXCL_START
g_set_error(error, NETPLAN_FILE_ERROR, errno, "failed to create enablement symlink: %m\n");
return FALSE;
// LCOV_EXCL_STOP
}
return TRUE;
}
static gboolean
interval_has_suffix(const char* param) {
gchar* endptr;
g_ascii_strtoull(param, &endptr, 10);
if (*endptr == '\0')
return FALSE;
return TRUE;
}
static void
write_bond_parameters(const NetplanNetDefinition* def, GString* s)
{
GString* params = NULL;
params = g_string_sized_new(200);
if (def->bond_params.mode)
g_string_append_printf(params, "\nMode=%s", def->bond_params.mode);
if (def->bond_params.lacp_rate)
g_string_append_printf(params, "\nLACPTransmitRate=%s", def->bond_params.lacp_rate);
if (def->bond_params.monitor_interval) {
g_string_append(params, "\nMIIMonitorSec=");
if (interval_has_suffix(def->bond_params.monitor_interval))
g_string_append(params, def->bond_params.monitor_interval);
else
g_string_append_printf(params, "%sms", def->bond_params.monitor_interval);
}
if (def->bond_params.min_links)
g_string_append_printf(params, "\nMinLinks=%d", def->bond_params.min_links);
if (def->bond_params.transmit_hash_policy)
g_string_append_printf(params, "\nTransmitHashPolicy=%s", def->bond_params.transmit_hash_policy);
if (def->bond_params.selection_logic)
g_string_append_printf(params, "\nAdSelect=%s", def->bond_params.selection_logic);
if (def->bond_params.all_members_active)
g_string_append_printf(params, "\nAllSlavesActive=%d", def->bond_params.all_members_active); /* wokeignore:rule=slave */
if (def->bond_params.arp_interval) {
g_string_append(params, "\nARPIntervalSec=");
if (interval_has_suffix(def->bond_params.arp_interval))
g_string_append(params, def->bond_params.arp_interval);
else
g_string_append_printf(params, "%sms", def->bond_params.arp_interval);
}
if (def->bond_params.arp_ip_targets && def->bond_params.arp_ip_targets->len > 0) {
g_string_append_printf(params, "\nARPIPTargets=");
for (unsigned i = 0; i < def->bond_params.arp_ip_targets->len; ++i) {
if (i > 0)
g_string_append_printf(params, " ");
g_string_append_printf(params, "%s", g_array_index(def->bond_params.arp_ip_targets, char*, i));
}
}
if (def->bond_params.arp_validate)
g_string_append_printf(params, "\nARPValidate=%s", def->bond_params.arp_validate);
if (def->bond_params.arp_all_targets)
g_string_append_printf(params, "\nARPAllTargets=%s", def->bond_params.arp_all_targets);
if (def->bond_params.up_delay) {
g_string_append(params, "\nUpDelaySec=");
if (interval_has_suffix(def->bond_params.up_delay))
g_string_append(params, def->bond_params.up_delay);
else
g_string_append_printf(params, "%sms", def->bond_params.up_delay);
}
if (def->bond_params.down_delay) {
g_string_append(params, "\nDownDelaySec=");
if (interval_has_suffix(def->bond_params.down_delay))
g_string_append(params, def->bond_params.down_delay);
else
g_string_append_printf(params, "%sms", def->bond_params.down_delay);
}
if (def->bond_params.fail_over_mac_policy)
g_string_append_printf(params, "\nFailOverMACPolicy=%s", def->bond_params.fail_over_mac_policy);
if (def->bond_params.gratuitous_arp)
g_string_append_printf(params, "\nGratuitousARP=%d", def->bond_params.gratuitous_arp);
/* TODO: add unsolicited_na, not documented as supported by NM. */
if (def->bond_params.packets_per_member)
g_string_append_printf(params, "\nPacketsPerSlave=%d", def->bond_params.packets_per_member); /* wokeignore:rule=slave */
if (def->bond_params.primary_reselect_policy)
g_string_append_printf(params, "\nPrimaryReselectPolicy=%s", def->bond_params.primary_reselect_policy);
if (def->bond_params.resend_igmp)
g_string_append_printf(params, "\nResendIGMP=%d", def->bond_params.resend_igmp);
if (def->bond_params.learn_interval)
g_string_append_printf(params, "\nLearnPacketIntervalSec=%s", def->bond_params.learn_interval);
if (params->len)
g_string_append_printf(s, "\n[Bond]%s\n", params->str);
g_string_free(params, TRUE);
}
static void
write_vxlan_parameters(const NetplanNetDefinition* def, GString* s)
{
g_assert(def->vxlan);
GString* params = NULL;
params = g_string_sized_new(200);
if (def->tunnel.remote_ip) {
if (is_multicast_address(def->tunnel.remote_ip))
g_string_append_printf(params, "\nGroup=%s", def->tunnel.remote_ip);
else
g_string_append_printf(params, "\nRemote=%s", def->tunnel.remote_ip);
}
if (def->tunnel.local_ip)
g_string_append_printf(params, "\nLocal=%s", def->tunnel.local_ip);
if (def->vxlan->tos)
g_string_append_printf(params, "\nTOS=%d", def->vxlan->tos);
if (def->tunnel_ttl)
g_string_append_printf(params, "\nTTL=%d", def->tunnel_ttl);
if (def->vxlan->mac_learning != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(params, "\nMacLearning=%s", def->vxlan->mac_learning ? "true" : "false");
if (def->vxlan->ageing)
g_string_append_printf(params, "\nFDBAgeingSec=%d", def->vxlan->ageing);
if (def->vxlan->limit)
g_string_append_printf(params, "\nMaximumFDBEntries=%d", def->vxlan->limit);
if (def->vxlan->arp_proxy != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(params, "\nReduceARPProxy=%s", def->vxlan->arp_proxy ? "true" : "false");
if (def->vxlan->notifications) {
if (def->vxlan->notifications & NETPLAN_VXLAN_NOTIFICATION_L2_MISS)
g_string_append(params, "\nL2MissNotification=true");
if (def->vxlan->notifications & NETPLAN_VXLAN_NOTIFICATION_L3_MISS)
g_string_append(params, "\nL3MissNotification=true");
}
if (def->vxlan->short_circuit != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(params, "\nRouteShortCircuit=%s", def->vxlan->short_circuit ? "true" : "false");
if (def->vxlan->checksums) {
if (def->vxlan->checksums & NETPLAN_VXLAN_CHECKSUM_UDP)
g_string_append(params, "\nUDPChecksum=true");
if (def->vxlan->checksums & NETPLAN_VXLAN_CHECKSUM_ZERO_UDP6_TX)
g_string_append(params, "\nUDP6ZeroChecksumTx=true");
if (def->vxlan->checksums & NETPLAN_VXLAN_CHECKSUM_ZERO_UDP6_RX)
g_string_append(params, "\nUDP6ZeroChecksumRx=true");
if (def->vxlan->checksums & NETPLAN_VXLAN_CHECKSUM_REMOTE_TX)
g_string_append(params, "\nRemoteChecksumTx=true");
if (def->vxlan->checksums & NETPLAN_VXLAN_CHECKSUM_REMOTE_RX)
g_string_append(params, "\nRemoteChecksumRx=true");
}
if (def->vxlan->extensions) {
if (def->vxlan->extensions & NETPLAN_VXLAN_EXTENSION_GROUP_POLICY)
g_string_append(params, "\nGroupPolicyExtension=true");
if (def->vxlan->extensions & NETPLAN_VXLAN_EXTENSION_GENERIC_PROTOCOL)
g_string_append(params, "\nGenericProtocolExtension=true");
}
if (def->tunnel.port)
g_string_append_printf(params, "\nDestinationPort=%d", def->tunnel.port);
if (def->vxlan->source_port_min && def->vxlan->source_port_max)
g_string_append_printf(params, "\nPortRange=%u-%u",
def->vxlan->source_port_min,
def->vxlan->source_port_max);
if (def->vxlan->flow_label != G_MAXUINT)
g_string_append_printf(params, "\nFlowLabel=%d", def->vxlan->flow_label);
if (def->vxlan->do_not_fragment != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(params, "\nIPDoNotFragment=%s", def->vxlan->do_not_fragment ? "true" : "false");
if (!def->vxlan->link)
g_string_append(params, "\nIndependent=true");
if (params->len)
g_string_append_printf(s, "%s\n", params->str);
g_string_free(params, TRUE);
}
static void
write_netdev_file(const NetplanNetDefinition* def, const char* rootdir, const char* path)
{
GString* s = NULL;
mode_t orig_umask;
g_assert(def->type >= NETPLAN_DEF_TYPE_VIRTUAL);
if (def->type == NETPLAN_DEF_TYPE_VLAN && def->sriov_vlan_filter) {
g_debug("%s is defined as a hardware SR-IOV filtered VLAN, postponing creation", def->id);
return;
}
/* build file contents */
s = g_string_sized_new(200);
g_string_append_printf(s, "[NetDev]\nName=%s\n", def->id);
if (def->set_mac)
g_string_append_printf(s, "MACAddress=%s\n", def->set_mac);
if (def->mtubytes)
g_string_append_printf(s, "MTUBytes=%u\n", def->mtubytes);
switch (def->type) {
case NETPLAN_DEF_TYPE_BRIDGE:
g_string_append(s, "Kind=bridge\n");
write_bridge_params_networkd(s, def);
break;
case NETPLAN_DEF_TYPE_BOND:
g_string_append(s, "Kind=bond\n");
write_bond_parameters(def, s);
break;
case NETPLAN_DEF_TYPE_VLAN:
g_string_append_printf(s, "Kind=vlan\n\n[VLAN]\nId=%u\n", def->vlan_id);
break;
case NETPLAN_DEF_TYPE_VRF:
g_string_append_printf(s, "Kind=vrf\n\n[VRF]\nTable=%u\n", def->vrf_table);
break;
case NETPLAN_DEF_TYPE_DUMMY: /* wokeignore:rule=dummy */
g_string_append_printf(s, "Kind=dummy\n"); /* wokeignore:rule=dummy */
break;
case NETPLAN_DEF_TYPE_VETH:
/*
* Only one .netdev file is required to create the veth pair.
* To select what netdef we are going to use, we sort both names, get the first one,
* and, if the selected name is the name of the netdef being written, we generate
* the .netdev file. Otherwise we skip the netdef.
*/
gchar* first = g_strcmp0(def->id, def->veth_peer_link->id) < 0 ? def->id : def->veth_peer_link->id;
if (first != def->id) {
g_string_free(s, TRUE);
return;
}
g_string_append_printf(s, "Kind=veth\n\n[Peer]\nName=%s\n", def->veth_peer_link->id);
break;
case NETPLAN_DEF_TYPE_TUNNEL:
switch(def->tunnel.mode) {
case NETPLAN_TUNNEL_MODE_GRE:
case NETPLAN_TUNNEL_MODE_GRETAP:
case NETPLAN_TUNNEL_MODE_IPIP:
case NETPLAN_TUNNEL_MODE_IP6GRE:
case NETPLAN_TUNNEL_MODE_IP6GRETAP:
case NETPLAN_TUNNEL_MODE_SIT:
case NETPLAN_TUNNEL_MODE_VTI:
case NETPLAN_TUNNEL_MODE_VTI6:
case NETPLAN_TUNNEL_MODE_WIREGUARD:
g_string_append_printf(s, "Kind=%s\n",
netplan_tunnel_mode_name(def->tunnel.mode));
break;
case NETPLAN_TUNNEL_MODE_VXLAN:
g_string_append_printf(s, "Kind=vxlan\n\n[VXLAN]\nVNI=%u", def->vxlan->vni);
break;
case NETPLAN_TUNNEL_MODE_IP6IP6:
case NETPLAN_TUNNEL_MODE_IPIP6:
g_string_append(s, "Kind=ip6tnl\n");
break;
// LCOV_EXCL_START
default:
g_assert_not_reached();
// LCOV_EXCL_STOP
}
if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD)
write_wireguard_params(s, def);
else if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_VXLAN)
write_vxlan_parameters(def, s);
else
write_tunnel_params(s, def);
break;
default: g_assert_not_reached(); // LCOV_EXCL_LINE
}
/* these do not contain secrets and need to be readable by
* systemd-networkd - LP: #1736965 */
orig_umask = umask(022);
g_string_free_to_file(s, rootdir, path, ".netdev");
umask(orig_umask);
}
static void
write_route(NetplanIPRoute* r, GString* s)
{
const char *to;
g_string_append_printf(s, "\n[Route]\n");
if (g_strcmp0(r->to, "default") == 0)
to = get_global_network(r->family);
else
to = r->to;
g_string_append_printf(s, "Destination=%s\n", to);
if (r->via)
g_string_append_printf(s, "Gateway=%s\n", r->via);
if (r->from)
g_string_append_printf(s, "PreferredSource=%s\n", r->from);
if (g_strcmp0(r->scope, "global") != 0)
g_string_append_printf(s, "Scope=%s\n", r->scope);
if (g_strcmp0(r->type, "unicast") != 0)
g_string_append_printf(s, "Type=%s\n", r->type);
if (r->onlink)
g_string_append_printf(s, "GatewayOnLink=true\n");
if (r->metric != NETPLAN_METRIC_UNSPEC)
g_string_append_printf(s, "Metric=%u\n", r->metric);
if (r->table != NETPLAN_ROUTE_TABLE_UNSPEC)
g_string_append_printf(s, "Table=%d\n", r->table);
if (r->mtubytes != NETPLAN_MTU_UNSPEC)
g_string_append_printf(s, "MTUBytes=%u\n", r->mtubytes);
if (r->congestion_window != NETPLAN_CONGESTION_WINDOW_UNSPEC)
g_string_append_printf(s, "InitialCongestionWindow=%u\n", r->congestion_window);
if (r->advertised_receive_window != NETPLAN_ADVERTISED_RECEIVE_WINDOW_UNSPEC)
g_string_append_printf(s, "InitialAdvertisedReceiveWindow=%u\n", r->advertised_receive_window);
}
static void
write_ip_rule(NetplanIPRule* r, GString* s)
{
g_string_append_printf(s, "\n[RoutingPolicyRule]\n");
if (r->from)
g_string_append_printf(s, "From=%s\n", r->from);
if (r->to)
g_string_append_printf(s, "To=%s\n", r->to);
if (r->table != NETPLAN_ROUTE_TABLE_UNSPEC)
g_string_append_printf(s, "Table=%d\n", r->table);
if (r->priority != NETPLAN_IP_RULE_PRIO_UNSPEC)
g_string_append_printf(s, "Priority=%d\n", r->priority);
if (r->fwmark != NETPLAN_IP_RULE_FW_MARK_UNSPEC)
g_string_append_printf(s, "FirewallMark=%d\n", r->fwmark);
if (r->tos != NETPLAN_IP_RULE_TOS_UNSPEC)
g_string_append_printf(s, "TypeOfService=%d\n", r->tos);
}
static void
write_addr_option(NetplanAddressOptions* o, GString* s)
{
g_string_append_printf(s, "\n[Address]\n");
g_assert(o->address);
g_string_append_printf(s, "Address=%s\n", o->address);
if (o->lifetime)
g_string_append_printf(s, "PreferredLifetime=%s\n", o->lifetime);
if (o->label)
g_string_append_printf(s, "Label=%s\n", o->label);
}
#define DHCP_OVERRIDES_ERROR \
"ERROR: %s: networkd requires that %s has the same value in both " \
"dhcp4_overrides and dhcp6_overrides\n"
static gboolean
combine_dhcp_overrides(const NetplanNetDefinition* def, NetplanDHCPOverrides* combined_dhcp_overrides, GError** error)
{
/* if only one of dhcp4 or dhcp6 is enabled, those overrides are used */
if (def->dhcp4 && !def->dhcp6) {
*combined_dhcp_overrides = def->dhcp4_overrides;
} else if (!def->dhcp4 && def->dhcp6) {
*combined_dhcp_overrides = def->dhcp6_overrides;
} else {
/* networkd doesn't support separately configuring dhcp4 and dhcp6, so
* we enforce that they are the same.
*/
if (def->dhcp4_overrides.use_dns != def->dhcp6_overrides.use_dns) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, DHCP_OVERRIDES_ERROR, def->id, "use-dns");
return FALSE;
}
if (g_strcmp0(def->dhcp4_overrides.use_domains, def->dhcp6_overrides.use_domains) != 0){
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, DHCP_OVERRIDES_ERROR, def->id, "use-domains");
return FALSE;
}
if (def->dhcp4_overrides.use_ntp != def->dhcp6_overrides.use_ntp) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, DHCP_OVERRIDES_ERROR, def->id, "use-ntp");
return FALSE;
}
if (def->dhcp4_overrides.send_hostname != def->dhcp6_overrides.send_hostname) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, DHCP_OVERRIDES_ERROR, def->id, "send-hostname");
return FALSE;
}
if (def->dhcp4_overrides.use_hostname != def->dhcp6_overrides.use_hostname) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, DHCP_OVERRIDES_ERROR, def->id, "use-hostname");
return FALSE;
}
if (def->dhcp4_overrides.use_mtu != def->dhcp6_overrides.use_mtu) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, DHCP_OVERRIDES_ERROR, def->id, "use-mtu");
return FALSE;
}
if (g_strcmp0(def->dhcp4_overrides.hostname, def->dhcp6_overrides.hostname) != 0) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, DHCP_OVERRIDES_ERROR, def->id, "hostname");
return FALSE;
}
if (def->dhcp4_overrides.metric != def->dhcp6_overrides.metric) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, DHCP_OVERRIDES_ERROR, def->id, "route-metric");
return FALSE;
}
if (def->dhcp4_overrides.use_routes != def->dhcp6_overrides.use_routes) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, DHCP_OVERRIDES_ERROR, def->id, "use-routes");
return FALSE;
}
/* Just use dhcp4_overrides now, since we know they are the same. */
*combined_dhcp_overrides = def->dhcp4_overrides;
}
return TRUE;
}
/**
* Write the needed networkd .network configuration for the selected netplan definition.
*/
gboolean
netplan_netdef_write_network_file(
const NetplanState* np_state,
const NetplanNetDefinition* def,
const char *rootdir,
const char* path,
gboolean* has_been_written,
GError** error)
{
g_autoptr(GString) network = NULL;
g_autoptr(GString) link = NULL;
GString* s = NULL;
mode_t orig_umask;
gboolean is_optional = def->optional;
SET_OPT_OUT_PTR(has_been_written, FALSE);
if (def->type == NETPLAN_DEF_TYPE_VLAN && def->sriov_vlan_filter) {
g_debug("%s is defined as a hardware SR-IOV filtered VLAN, postponing creation", def->id);
return TRUE;
}
/* Prepare the [Link] section of the .network file. */
link = g_string_sized_new(200);
/* Prepare the [Network] section */
network = g_string_sized_new(200);
/* The ActivationPolicy setting is available in systemd v248+ */
if (def->activation_mode) {
const char* mode;
if (g_strcmp0(def->activation_mode, "manual") == 0)
mode = "manual";
else /* "off" */
mode = "always-down";
g_string_append_printf(link, "ActivationPolicy=%s\n", mode);
/* When activation-mode is used we default to being optional.
* Otherwise systemd might wait indefinitely for the interface to
* become online.
*/
is_optional = TRUE;
}
if (is_optional || def->optional_addresses) {
if (is_optional) {
g_string_append(link, "RequiredForOnline=no\n");
}
for (unsigned i = 0; NETPLAN_OPTIONAL_ADDRESS_TYPES[i].name != NULL; ++i) {
if (def->optional_addresses & NETPLAN_OPTIONAL_ADDRESS_TYPES[i].flag) {
g_string_append_printf(link, "OptionalAddresses=%s\n", NETPLAN_OPTIONAL_ADDRESS_TYPES[i].name);
}
}
}
if (def->mtubytes)
g_string_append_printf(link, "MTUBytes=%u\n", def->mtubytes);
if (def->set_mac)
g_string_append_printf(link, "MACAddress=%s\n", def->set_mac);
if (def->emit_lldp)
g_string_append(network, "EmitLLDP=true\n");
if (def->dhcp4 && def->dhcp6)
g_string_append(network, "DHCP=yes\n");
else if (def->dhcp4)
g_string_append(network, "DHCP=ipv4\n");
else if (def->dhcp6)
g_string_append(network, "DHCP=ipv6\n");
/* Set link local addressing -- this does not apply to bond and bridge
* member interfaces, which always get it disabled.
*/
if (!def->bond && !def->bridge && (def->linklocal.ipv4 || def->linklocal.ipv6)) {
if (def->linklocal.ipv4 && def->linklocal.ipv6)
g_string_append(network, "LinkLocalAddressing=yes\n");
else if (def->linklocal.ipv4)
g_string_append(network, "LinkLocalAddressing=ipv4\n");
else if (def->linklocal.ipv6)
g_string_append(network, "LinkLocalAddressing=ipv6\n");
} else {
g_string_append(network, "LinkLocalAddressing=no\n");
}
if (def->ip4_addresses)
for (unsigned i = 0; i < def->ip4_addresses->len; ++i)
g_string_append_printf(network, "Address=%s\n", g_array_index(def->ip4_addresses, char*, i));
if (def->ip6_addresses)
for (unsigned i = 0; i < def->ip6_addresses->len; ++i)
g_string_append_printf(network, "Address=%s\n", g_array_index(def->ip6_addresses, char*, i));
if (def->ip6_addr_gen_token) {
g_string_append_printf(network, "IPv6Token=static:%s\n", def->ip6_addr_gen_token);
} else if (def->ip6_addr_gen_mode > NETPLAN_ADDRGEN_EUI64) {
/* EUI-64 mode is enabled by default, if no IPv6Token= is specified */
/* TODO: Enable stable-privacy mode for networkd, once PR#16618 has been released:
* https://github.com/systemd/systemd/pull/16618 */
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: ipv6-address-generation mode is not supported by networkd\n", def->id);
return FALSE;
}
if (def->accept_ra == NETPLAN_RA_MODE_ENABLED)
g_string_append_printf(network, "IPv6AcceptRA=yes\n");
else if (def->accept_ra == NETPLAN_RA_MODE_DISABLED)
g_string_append_printf(network, "IPv6AcceptRA=no\n");
if (def->ip6_privacy)
g_string_append(network, "IPv6PrivacyExtensions=yes\n");
if (def->gateway4)
g_string_append_printf(network, "Gateway=%s\n", def->gateway4);
if (def->gateway6)
g_string_append_printf(network, "Gateway=%s\n", def->gateway6);
if (def->ip4_nameservers)
for (unsigned i = 0; i < def->ip4_nameservers->len; ++i)
g_string_append_printf(network, "DNS=%s\n", g_array_index(def->ip4_nameservers, char*, i));
if (def->ip6_nameservers)
for (unsigned i = 0; i < def->ip6_nameservers->len; ++i)
g_string_append_printf(network, "DNS=%s\n", g_array_index(def->ip6_nameservers, char*, i));
if (def->search_domains) {
g_string_append_printf(network, "Domains=%s", g_array_index(def->search_domains, char*, 0));
for (unsigned i = 1; i < def->search_domains->len; ++i)
g_string_append_printf(network, " %s", g_array_index(def->search_domains, char*, i));
g_string_append(network, "\n");
}
if (def->ipv6_mtubytes) {
g_string_append_printf(network, "IPv6MTUBytes=%d\n", def->ipv6_mtubytes);
}
if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL || def->ignore_carrier)
g_string_append(network, "ConfigureWithoutCarrier=yes\n");
if (def->critical)
g_string_append_printf(network, "KeepConfiguration=true\n");
if (def->bridge && def->backend != NETPLAN_BACKEND_OVS) {
g_string_append_printf(network, "Bridge=%s\n", def->bridge);
if ( def->bridge_params.path_cost
|| def->bridge_params.port_priority
|| def->bridge_neigh_suppress != NETPLAN_TRISTATE_UNSET)
g_string_append_printf(network, "\n[Bridge]\n");
if (def->bridge_params.path_cost)
g_string_append_printf(network, "Cost=%u\n", def->bridge_params.path_cost);
if (def->bridge_params.port_priority)
g_string_append_printf(network, "Priority=%u\n", def->bridge_params.port_priority);
if (def->bridge_neigh_suppress != NETPLAN_TRISTATE_UNSET) {
g_string_append_printf(network, "NeighborSuppression=%s\n", def->bridge_neigh_suppress ? "true" : "false");
}
}
if (def->bond && def->backend != NETPLAN_BACKEND_OVS) {
g_string_append_printf(network, "Bond=%s\n", def->bond);
if (def->bond_params.primary_member)
g_string_append_printf(network, "PrimarySlave=true\n"); /* wokeignore:rule=slave */
}
if (def->has_vlans && def->backend != NETPLAN_BACKEND_OVS) {
/* iterate over all netdefs to find VLANs attached to us */
GList *l = np_state->netdefs_ordered;
const NetplanNetDefinition* nd;
for (; l != NULL; l = l->next) {
nd = l->data;
if (nd->vlan_link == def && !nd->sriov_vlan_filter)
g_string_append_printf(network, "VLAN=%s\n", nd->id);
}
}
/* VRF linkage */
if (def->vrf_link)
g_string_append_printf(network, "VRF=%s\n", def->vrf_link->id);
/* VXLAN options */
if (def->has_vxlans) {
/* iterate over all netdefs to find VXLANs attached to us */
GList *l = np_state->netdefs_ordered;
const NetplanNetDefinition* nd;
for (; l != NULL; l = l->next) {
nd = l->data;
if (nd->vxlan && nd->vxlan->link == def &&
nd->type == NETPLAN_DEF_TYPE_TUNNEL &&
nd->tunnel.mode == NETPLAN_TUNNEL_MODE_VXLAN)
g_string_append_printf(network, "VXLAN=%s\n", nd->id);
}
}
if (def->routes != NULL) {
for (unsigned i = 0; i < def->routes->len; ++i) {
NetplanIPRoute* cur_route = g_array_index (def->routes, NetplanIPRoute*, i);
write_route(cur_route, network);
}
}
if (def->ip_rules != NULL) {
for (unsigned i = 0; i < def->ip_rules->len; ++i) {
NetplanIPRule* cur_rule = g_array_index (def->ip_rules, NetplanIPRule*, i);
write_ip_rule(cur_rule, network);
}
}
if (def->address_options) {
for (unsigned i = 0; i < def->address_options->len; ++i) {
NetplanAddressOptions* opts = g_array_index(def->address_options, NetplanAddressOptions*, i);
write_addr_option(opts, network);
}
}
if (def->dhcp4 || def->dhcp6) {
/* NetworkManager compatible route metrics */
g_string_append(network, "\n[DHCP]\n");
}
if (def->dhcp4 || def->dhcp6) {
if (def->dhcp_identifier)
g_string_append_printf(network, "ClientIdentifier=%s\n", def->dhcp_identifier);
NetplanDHCPOverrides combined_dhcp_overrides;
if (!combine_dhcp_overrides(def, &combined_dhcp_overrides, error))
return FALSE;
if (combined_dhcp_overrides.metric == NETPLAN_METRIC_UNSPEC) {
g_string_append_printf(network, "RouteMetric=%i\n", (def->type == NETPLAN_DEF_TYPE_WIFI ? 600 : 100));
} else {
g_string_append_printf(network, "RouteMetric=%u\n",
combined_dhcp_overrides.metric);
}
/* Only set MTU from DHCP if use-mtu dhcp-override is not false. */
if (!combined_dhcp_overrides.use_mtu) {
/* isc-dhcp dhclient compatible UseMTU, networkd default is to
* not accept MTU, which breaks clouds */
g_string_append_printf(network, "UseMTU=false\n");
} else {
g_string_append_printf(network, "UseMTU=true\n");
}
/* Only write DHCP options that differ from the networkd default. */
if (!combined_dhcp_overrides.use_routes)
g_string_append_printf(network, "UseRoutes=false\n");
if (!combined_dhcp_overrides.use_dns)
g_string_append_printf(network, "UseDNS=false\n");
if (combined_dhcp_overrides.use_domains)
g_string_append_printf(network, "UseDomains=%s\n", combined_dhcp_overrides.use_domains);
if (!combined_dhcp_overrides.use_ntp)
g_string_append_printf(network, "UseNTP=false\n");
if (!combined_dhcp_overrides.send_hostname)
g_string_append_printf(network, "SendHostname=false\n");
if (!combined_dhcp_overrides.use_hostname)
g_string_append_printf(network, "UseHostname=false\n");
if (combined_dhcp_overrides.hostname)
g_string_append_printf(network, "Hostname=%s\n", combined_dhcp_overrides.hostname);
}
/* IP-over-InfiniBand, IPoIB */
if (def->ib_mode != NETPLAN_IB_MODE_KERNEL) {
g_string_append_printf(network, "\n[IPoIB]\nMode=%s\n", netplan_infiniband_mode_name(def->ib_mode));
}
if (network->len > 0 || link->len > 0) {
s = g_string_sized_new(200);
append_match_section(def, s, TRUE);
if (link->len > 0)
g_string_append_printf(s, "\n[Link]\n%s", link->str);
if (network->len > 0)
g_string_append_printf(s, "\n[Network]\n%s", network->str);
/* these do not contain secrets and need to be readable by
* systemd-networkd - LP: #1736965 */
orig_umask = umask(022);
g_string_free_to_file(s, rootdir, path, ".network");
umask(orig_umask);
}
SET_OPT_OUT_PTR(has_been_written, TRUE);
return TRUE;
}
static void
write_rules_file(const NetplanNetDefinition* def, const char* rootdir)
{
GString* s = NULL;
g_autofree char* path = g_strjoin(NULL, "run/udev/rules.d/99-netplan-", def->id, ".rules", NULL);
mode_t orig_umask;
/* do we need to write a .rules file?
* It's only required for reliably setting the name of a physical device
* until systemd issue #9006 is resolved. */
if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL)
return;
/* Matching by name does not work.
*
* As far as I can tell, if you match by the name coming out of
* initrd, systemd complains that a link file is matching on a
* renamed name. If you match by the unstable kernel name, the
* device no longer has that name when udevd reads the file, so
* the rule doesn't fire. So only support mac and driver. */
if (!def->set_name || (!def->match.mac && !def->match.driver))
return;
/* build file contents */
s = g_string_sized_new(200);
g_string_append(s, "SUBSYSTEM==\"net\", ACTION==\"add\", ");
if (def->match.driver) {
g_string_append_printf(s,"DRIVERS==\"%s\", ", def->match.driver);
} else {
g_string_append(s, "DRIVERS==\"?*\", ");
}
if (def->match.mac)
g_string_append_printf(s, "ATTR{address}==\"%s\", ", def->match.mac);
g_string_append_printf(s, "NAME=\"%s\"\n", def->set_name);
orig_umask = umask(022);
g_string_free_to_file(s, rootdir, path, NULL);
umask(orig_umask);
}
static gboolean
append_wpa_auth_conf(GString* s, const NetplanAuthenticationSettings* auth, const char* id, GError** error)
{
switch (auth->key_management) {
case NETPLAN_AUTH_KEY_MANAGEMENT_NONE:
g_string_append(s, " key_mgmt=NONE\n");
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK:
if (auth->pmf_mode == NETPLAN_AUTH_PMF_MODE_OPTIONAL)
/* Case where the user only provided the password.
* We enable support for WPA2 and WPA3 personal.
*/
g_string_append(s, " key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE\n");
else
g_string_append(s, " key_mgmt=WPA-PSK\n");
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP:
g_string_append(s, " key_mgmt=WPA-EAP\n");
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSHA256:
g_string_append(s, " key_mgmt=WPA-EAP WPA-EAP-SHA256\n");
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSUITE_B_192:
g_string_append(s, " key_mgmt=WPA-EAP-SUITE-B-192\n");
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_SAE:
g_string_append(s, " key_mgmt=SAE\n");
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_8021X:
g_string_append(s, " key_mgmt=IEEE8021X\n");
break;
default: break; // LCOV_EXCL_LINE
}
switch (auth->eap_method) {
case NETPLAN_AUTH_EAP_NONE:
break;
case NETPLAN_AUTH_EAP_TLS:
g_string_append(s, " eap=TLS\n");
break;
case NETPLAN_AUTH_EAP_PEAP:
g_string_append(s, " eap=PEAP\n");
break;
case NETPLAN_AUTH_EAP_TTLS:
g_string_append(s, " eap=TTLS\n");
break;
case NETPLAN_AUTH_EAP_LEAP:
g_string_append(s, " eap=LEAP\n");
break;
case NETPLAN_AUTH_EAP_PWD:
g_string_append(s, " eap=PWD\n");
break;
default: break; // LCOV_EXCL_LINE
}
switch (auth->pmf_mode) {
case NETPLAN_AUTH_PMF_MODE_NONE:
case NETPLAN_AUTH_PMF_MODE_DISABLED:
break;
case NETPLAN_AUTH_PMF_MODE_OPTIONAL:
g_string_append(s, " ieee80211w=1\n");
break;
case NETPLAN_AUTH_PMF_MODE_REQUIRED:
g_string_append(s, " ieee80211w=2\n");
break;
}
if (auth->identity) {
g_string_append_printf(s, " identity=\"%s\"\n", auth->identity);
}
if (auth->anonymous_identity) {
g_string_append_printf(s, " anonymous_identity=\"%s\"\n", auth->anonymous_identity);
}
char* psk = NULL;
if (auth->psk)
psk = auth->psk;
else if (auth->password && _is_auth_key_management_psk(auth))
psk = auth->password;
if (psk) {
size_t len = strlen(psk);
if (len == 64) {
/* must be a hex-digit key representation */
for (unsigned i = 0; i < 64; ++i)
if (!isxdigit(psk[i])) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: PSK length of 64 is only supported for hex-digit representation\n", id);
return FALSE;
}
/* this is required to be unquoted */
g_string_append_printf(s, " psk=%s\n", psk);
} else if (len < 8 || len > 63) {
/* per wpa_supplicant spec, passphrase needs to be between 8 and 63 characters */
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, "ERROR: %s: ASCII passphrase must be between 8 and 63 characters (inclusive)\n", id);
return FALSE;
} else {
g_string_append_printf(s, " psk=\"%s\"\n", psk);
}
}
if (auth->password
&& (!_is_auth_key_management_psk(auth) || auth->eap_method != NETPLAN_AUTH_EAP_NONE)) {
if (strncmp(auth->password, "hash:", 5) == 0) {
g_string_append_printf(s, " password=%s\n", auth->password);
} else {
g_string_append_printf(s, " password=\"%s\"\n", auth->password);
}
}
if (auth->ca_certificate) {
g_string_append_printf(s, " ca_cert=\"%s\"\n", auth->ca_certificate);
}
if (auth->client_certificate) {
g_string_append_printf(s, " client_cert=\"%s\"\n", auth->client_certificate);
}
if (auth->client_key) {
g_string_append_printf(s, " private_key=\"%s\"\n", auth->client_key);
}
if (auth->client_key_password) {
g_string_append_printf(s, " private_key_passwd=\"%s\"\n", auth->client_key_password);
}
if (auth->phase2_auth) {
g_string_append_printf(s, " phase2=\"auth=%s\"\n", auth->phase2_auth);
}
return TRUE;
}
/* netplan-feature: generated-supplicant */
static void
write_wpa_unit(const NetplanNetDefinition* def, const char* rootdir)
{
g_autofree gchar *stdouth = NULL;
mode_t orig_umask;
stdouth = systemd_escape(def->id);
GString* s = g_string_new("[Unit]\n");
g_autofree char* path = g_strjoin(NULL, "/run/systemd/system/netplan-wpa-", stdouth, ".service", NULL);
g_string_append_printf(s, "Description=WPA supplicant for netplan %s\n", stdouth);
g_string_append(s, "DefaultDependencies=no\n");
g_string_append_printf(s, "Requires=sys-subsystem-net-devices-%s.device\n", stdouth);
g_string_append_printf(s, "After=sys-subsystem-net-devices-%s.device\n", stdouth);
g_string_append(s, "Before=network.target\nWants=network.target\n\n");
g_string_append(s, "[Service]\nType=simple\n");
g_string_append_printf(s, "ExecStart=/sbin/wpa_supplicant -c /run/netplan/wpa-%s.conf -i%s", stdouth, stdouth);
if (def->type != NETPLAN_DEF_TYPE_WIFI) {
g_string_append(s, " -Dwired\n");
} else {
g_string_append(s, " -Dnl80211,wext\n");
}
orig_umask = umask(022);
g_string_free_to_file(s, rootdir, path, NULL);
umask(orig_umask);
}
static gboolean
write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir, GError** error)
{
GHashTableIter iter;
GString* s = g_string_new("ctrl_interface=/run/wpa_supplicant\n\n");
g_autofree char* path = g_strjoin(NULL, "run/netplan/wpa-", def->id, ".conf", NULL);
mode_t orig_umask;
g_debug("%s: Creating wpa_supplicant configuration file %s", def->id, path);
if (def->type == NETPLAN_DEF_TYPE_WIFI) {
if (def->wowlan && def->wowlan > NETPLAN_WIFI_WOWLAN_DEFAULT) {
g_string_append(s, "wowlan_triggers=");
if (!append_wifi_wowlan_flags(def->wowlan, s, error)) {
g_string_free(s, TRUE);
return FALSE;
}
}
/* available as of wpa_supplicant version 0.6.7 */
if (def->regulatory_domain) {
g_string_append_printf(s, "country=%s\n", def->regulatory_domain);
}
NetplanWifiAccessPoint* ap;
g_hash_table_iter_init(&iter, def->access_points);
while (g_hash_table_iter_next(&iter, NULL, (gpointer) &ap)) {
gchar* freq_config_str = ap->mode == NETPLAN_WIFI_MODE_ADHOC ? "frequency" : "freq_list";
g_string_append_printf(s, "network={\n ssid=\"%s\"\n", ap->ssid);
if (ap->bssid) {
g_string_append_printf(s, " bssid=%s\n", ap->bssid);
}
if (ap->hidden) {
g_string_append(s, " scan_ssid=1\n");
}
if (ap->band == NETPLAN_WIFI_BAND_24) {
// initialize 2.4GHz frequency hashtable
if(!wifi_frequency_24)
wifi_get_freq24(1);
if (ap->channel) {
g_string_append_printf(s, " %s=%d\n", freq_config_str, wifi_get_freq24(ap->channel));
} else if (ap->mode != NETPLAN_WIFI_MODE_ADHOC) {
g_string_append_printf(s, " freq_list=");
g_hash_table_foreach(wifi_frequency_24, wifi_append_freq, s);
// overwrite last whitespace with newline
s = g_string_overwrite(s, s->len-1, "\n");
}
} else if (ap->band == NETPLAN_WIFI_BAND_5) {
// initialize 5GHz frequency hashtable
if(!wifi_frequency_5)
wifi_get_freq5(7);
if (ap->channel) {
g_string_append_printf(s, " %s=%d\n", freq_config_str, wifi_get_freq5(ap->channel));
} else if (ap->mode != NETPLAN_WIFI_MODE_ADHOC) {
g_string_append_printf(s, " freq_list=");
g_hash_table_foreach(wifi_frequency_5, wifi_append_freq, s);
// overwrite last whitespace with newline
s = g_string_overwrite(s, s->len-1, "\n");
}
}
switch (ap->mode) {
case NETPLAN_WIFI_MODE_INFRASTRUCTURE:
/* default in wpasupplicant */
break;
case NETPLAN_WIFI_MODE_ADHOC:
g_string_append(s, " mode=1\n");
break;
default:
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: %s: networkd does not support this wifi mode\n", def->id, ap->ssid);
g_string_free(s, TRUE);
return FALSE;
}
/* wifi auth trumps netdef auth */
if (ap->has_auth) {
if (!append_wpa_auth_conf(s, &ap->auth, ap->ssid, error)) {
g_string_free(s, TRUE);
return FALSE;
}
}
else {
g_string_append(s, " key_mgmt=NONE\n");
}
g_string_append(s, "}\n");
}
}
else {
/* wired 802.1x auth or similar */
g_string_append(s, "network={\n");
if (!append_wpa_auth_conf(s, &def->auth, def->id, error)) {
g_string_free(s, TRUE);
return FALSE;
}
g_string_append(s, "}\n");
}
/* use tight permissions as this contains secrets */
orig_umask = umask(077);
g_string_free_to_file(s, rootdir, path, NULL);
umask(orig_umask);
return TRUE;
}
/**
* Generate networkd configuration in @rootdir/run/systemd/network/ from the
* parsed #netdefs.
* @rootdir: If not %NULL, generate configuration in this root directory
* (useful for testing).
* @has_been_written: TRUE if @def applies to networkd, FALSE otherwise.
* Returns: FALSE on error.
*/
gboolean
netplan_netdef_write_networkd(
const NetplanState* np_state,
const NetplanNetDefinition* def,
const char *rootdir,
gboolean* has_been_written,
GError** error)
{
/* TODO: make use of netplan_netdef_get_output_filename() */
g_autofree char* path_base = g_strjoin(NULL, "run/systemd/network/10-netplan-", def->id, NULL);
SET_OPT_OUT_PTR(has_been_written, FALSE);
/* We want this for all backends when renaming, as *.link and *.rules files are
* evaluated by udev, not networkd itself or NetworkManager. The regulatory
* domain applies to all backends, too. */
write_link_file(def, rootdir, path_base);
write_rules_file(def, rootdir);
if (def->regulatory_domain)
write_regdom(def, rootdir, NULL); /* overwrites global regdom */
if (def->backend != NETPLAN_BACKEND_NETWORKD) {
g_debug("networkd: definition %s is not for us (backend %i)", def->id, def->backend);
return TRUE;
}
if (def->type == NETPLAN_DEF_TYPE_MODEM) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: networkd backend does not support GSM/CDMA modem configuration\n", def->id);
return FALSE;
}
if (def->type == NETPLAN_DEF_TYPE_WIFI || def->has_auth) {
g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa-", def->id, ".service", NULL);
g_autofree char* slink = g_strjoin(NULL, "/run/systemd/system/netplan-wpa-", def->id, ".service", NULL);
if (def->type == NETPLAN_DEF_TYPE_WIFI && def->has_match) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: networkd backend does not support wifi with match:, only by interface name\n", def->id);
return FALSE;
}
g_debug("Creating wpa_supplicant config");
if (!write_wpa_conf(def, rootdir, error))
return FALSE;
g_debug("Creating wpa_supplicant unit %s", slink);
write_wpa_unit(def, rootdir);
g_debug("Creating wpa_supplicant service enablement link %s", link);
safe_mkdir_p_dir(link);
if (symlink(slink, link) < 0 && errno != EEXIST) {
// LCOV_EXCL_START
g_set_error(error, NETPLAN_FILE_ERROR, errno, "failed to create enablement symlink: %m\n");
return FALSE;
// LCOV_EXCL_STOP
}
}
if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL)
write_netdev_file(def, rootdir, path_base);
if (!netplan_netdef_write_network_file(np_state, def, rootdir, path_base, has_been_written, error))
return FALSE;
SET_OPT_OUT_PTR(has_been_written, TRUE);
return TRUE;
}
/**
* Clean up all generated configurations in @rootdir from previous runs.
*/
void
netplan_networkd_cleanup(const char* rootdir)
{
unlink_glob(rootdir, "/run/systemd/network/10-netplan-*");
unlink_glob(rootdir, "/run/netplan/wpa-*.conf");
unlink_glob(rootdir, "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa-*.service");
unlink_glob(rootdir, "/run/systemd/system/netplan-wpa-*.service");
unlink_glob(rootdir, "/run/udev/rules.d/99-netplan-*");
unlink_glob(rootdir, "/run/systemd/system/network.target.wants/netplan-regdom.service");
unlink_glob(rootdir, "/run/systemd/system/netplan-regdom.service");
/* Historically (up to v0.98) we had netplan-wpa@*.service files, in case of an
* upgraded system, we need to make sure to clean those up. */
unlink_glob(rootdir, "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa@*.service");
}
netplan-0.107.1/src/networkd.h 0000664 0000000 0000000 00000002406 14536110317 0016121 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2016 Canonical, Ltd.
* Author: Martin Pitt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#pragma once
#include "netplan.h"
#include
NETPLAN_INTERNAL gboolean
netplan_netdef_write_networkd(
const NetplanState* np_state,
const NetplanNetDefinition* def,
const char *rootdir,
gboolean* has_been_written,
GError** error);
NETPLAN_INTERNAL gboolean
netplan_netdef_write_network_file(
const NetplanState* np_state,
const NetplanNetDefinition* def,
const char *rootdir,
const char* path,
gboolean* has_been_written,
GError** error);
NETPLAN_INTERNAL void
netplan_networkd_cleanup(const char* rootdir);
netplan-0.107.1/src/nm.c 0000664 0000000 0000000 00000146206 14536110317 0014700 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2016-2021 Canonical, Ltd.
* Author: Martin Pitt
* Author: Lukas Märdian
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "names.h"
#include "netplan.h"
#include "nm.h"
#include "parse.h"
#include "parse-globals.h"
#include "parse-nm.h"
#include "util.h"
#include "util-internal.h"
#include "validation.h"
/**
* Infer if this is a modem netdef of type GSM.
* This is done by checking for certain modem_params, which are only
* applicable to GSM connections.
*/
static gboolean
modem_is_gsm(const NetplanNetDefinition* def)
{
if ( def->modem_params.apn
|| def->modem_params.auto_config
|| def->modem_params.device_id
|| def->modem_params.network_id
|| def->modem_params.pin
|| def->modem_params.sim_id
|| def->modem_params.sim_operator_id)
return TRUE;
return FALSE;
}
/**
* Return NM "type=" string.
*/
static char*
type_str(const NetplanNetDefinition* def)
{
const NetplanDefType type = def->type;
switch (type) {
case NETPLAN_DEF_TYPE_ETHERNET:
/* 20-byte IPoIB MAC + colons */
if (def->ib_mode || (def->match.mac && strlen(def->match.mac) == 59))
return "infiniband";
else
return "ethernet";
case NETPLAN_DEF_TYPE_MODEM:
if (modem_is_gsm(def))
return "gsm";
else
return "cdma";
case NETPLAN_DEF_TYPE_WIFI:
return "wifi";
case NETPLAN_DEF_TYPE_BRIDGE:
return "bridge";
case NETPLAN_DEF_TYPE_BOND:
return "bond";
case NETPLAN_DEF_TYPE_VLAN:
return "vlan";
case NETPLAN_DEF_TYPE_VRF:
return "vrf";
case NETPLAN_DEF_TYPE_DUMMY: /* wokeignore:rule=dummy */
return "dummy"; /* wokeignore:rule=dummy */
case NETPLAN_DEF_TYPE_VETH:
return "veth";
case NETPLAN_DEF_TYPE_TUNNEL:
if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD)
return "wireguard";
else if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_VXLAN)
return "vxlan";
return "ip-tunnel";
case NETPLAN_DEF_TYPE_NM:
/* needs to be overriden by passthrough "connection.type" setting */
g_assert(def->backend_settings.passthrough);
GData *passthrough = def->backend_settings.passthrough;
return g_datalist_get_data(&passthrough, "connection.type");
// LCOV_EXCL_START
default:
g_assert_not_reached();
// LCOV_EXCL_STOP
}
}
/**
* Return NM wifi "mode=" string.
*/
static char*
wifi_mode_str(const NetplanWifiMode mode)
{
switch (mode) {
case NETPLAN_WIFI_MODE_INFRASTRUCTURE:
return "infrastructure";
case NETPLAN_WIFI_MODE_ADHOC:
return "adhoc";
case NETPLAN_WIFI_MODE_AP:
return "ap";
// LCOV_EXCL_START
default:
g_assert_not_reached();
// LCOV_EXCL_STOP
}
}
/**
* Return NM wifi "band=" string.
*/
static char*
wifi_band_str(const NetplanWifiBand band)
{
switch (band) {
case NETPLAN_WIFI_BAND_5:
return "a";
case NETPLAN_WIFI_BAND_24:
return "bg";
// LCOV_EXCL_START
default:
g_assert_not_reached();
// LCOV_EXCL_STOP
}
}
/**
* Return NM addr-gen-mode string.
*/
static char*
addr_gen_mode_str(const NetplanAddrGenMode mode)
{
switch (mode) {
case NETPLAN_ADDRGEN_EUI64:
return "0";
case NETPLAN_ADDRGEN_STABLEPRIVACY:
return "1";
// LCOV_EXCL_START
default:
g_assert_not_reached();
// LCOV_EXCL_STOP
}
}
static void
write_search_domains(const NetplanNetDefinition* def, const char* group, GKeyFile *kf)
{
if (def->search_domains) {
const gchar* list[def->search_domains->len];
for (unsigned i = 0; i < def->search_domains->len; ++i)
list[i] = g_array_index(def->search_domains, char*, i);
g_key_file_set_string_list(kf, group, "dns-search", list, def->search_domains->len);
}
}
static gboolean
write_routes_nm(const NetplanNetDefinition* def, GKeyFile *kf, gint family, GError** error)
{
const gchar* group = NULL;
gchar* tmp_key = NULL;
GString* tmp_val = NULL;
if (family == AF_INET)
group = "ipv4";
else if (family == AF_INET6)
group = "ipv6";
g_assert(group != NULL);
if (def->routes != NULL) {
for (unsigned i = 0, j = 1; i < def->routes->len; ++i) {
const NetplanIPRoute *cur_route = g_array_index(def->routes, NetplanIPRoute*, i);
const char *destination;
if (cur_route->family != family)
continue;
if (g_strcmp0(cur_route->to, "default") == 0)
destination = get_global_network(family);
else
destination = cur_route->to;
if (cur_route->type && g_ascii_strcasecmp(cur_route->type, "unicast") != 0) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: NetworkManager only supports unicast routes\n", def->id);
return FALSE;
}
/* For IPv6 addresses, kernel and NetworkManager don't support a scope.
* For IPv4 addresses, NetworkManager determines the scope of addresses on its own
* ("link"/"host" for addresses without gateway, "global" for addresses with next-hop).
* No gateway is represented as missing, empty or unspecified address in keyfile. */
gboolean is_global = (g_strcmp0(cur_route->scope, "global") == 0);
tmp_key = g_strdup_printf("route%d", j);
tmp_val = g_string_new(destination);
if (cur_route->metric != NETPLAN_METRIC_UNSPEC)
g_string_append_printf(tmp_val, ",%s,%u", is_global ? cur_route->via : "",
cur_route->metric);
else if (is_global) // no metric, but global gateway
g_string_append_printf(tmp_val, ",%s", cur_route->via);
g_key_file_set_string(kf, group, tmp_key, tmp_val->str);
g_free(tmp_key);
g_string_free(tmp_val, TRUE);
if ( cur_route->onlink
|| cur_route->advertised_receive_window
|| cur_route->congestion_window
|| cur_route->mtubytes
|| cur_route->table != NETPLAN_ROUTE_TABLE_UNSPEC
|| cur_route->from) {
tmp_key = g_strdup_printf("route%d_options", j);
tmp_val = g_string_new(NULL);
if (cur_route->onlink) {
/* onlink for IPv6 addresses is only supported since nm-1.18.0. */
g_string_append_printf(tmp_val, "onlink=true,");
}
if (cur_route->advertised_receive_window != NETPLAN_ADVERTISED_RECEIVE_WINDOW_UNSPEC)
g_string_append_printf(tmp_val, "initrwnd=%u,", cur_route->advertised_receive_window);
if (cur_route->congestion_window != NETPLAN_CONGESTION_WINDOW_UNSPEC)
g_string_append_printf(tmp_val, "initcwnd=%u,", cur_route->congestion_window);
if (cur_route->mtubytes != NETPLAN_MTU_UNSPEC)
g_string_append_printf(tmp_val, "mtu=%u,", cur_route->mtubytes);
if (cur_route->table != NETPLAN_ROUTE_TABLE_UNSPEC)
g_string_append_printf(tmp_val, "table=%u,", cur_route->table);
if (cur_route->from)
g_string_append_printf(tmp_val, "src=%s,", cur_route->from);
tmp_val->str[tmp_val->len - 1] = '\0'; //remove trailing comma
g_key_file_set_string(kf, group, tmp_key, tmp_val->str);
g_free(tmp_key);
g_string_free(tmp_val, TRUE);
}
j++;
}
}
return TRUE;
}
static void
write_bond_parameters(const NetplanNetDefinition* def, GKeyFile *kf)
{
GString* tmp_val = NULL;
if (def->bond_params.mode)
g_key_file_set_string(kf, "bond", "mode", def->bond_params.mode);
if (def->bond_params.lacp_rate)
g_key_file_set_string(kf, "bond", "lacp_rate", def->bond_params.lacp_rate);
if (def->bond_params.monitor_interval)
g_key_file_set_string(kf, "bond", "miimon", def->bond_params.monitor_interval);
if (def->bond_params.min_links)
g_key_file_set_integer(kf, "bond", "min_links", def->bond_params.min_links);
if (def->bond_params.transmit_hash_policy)
g_key_file_set_string(kf, "bond", "xmit_hash_policy", def->bond_params.transmit_hash_policy);
if (def->bond_params.selection_logic)
g_key_file_set_string(kf, "bond", "ad_select", def->bond_params.selection_logic);
if (def->bond_params.all_members_active)
g_key_file_set_integer(kf, "bond", "all_slaves_active", def->bond_params.all_members_active); /* wokeignore:rule=slave */
if (def->bond_params.arp_interval)
g_key_file_set_string(kf, "bond", "arp_interval", def->bond_params.arp_interval);
if (def->bond_params.arp_ip_targets) {
tmp_val = g_string_new(NULL);
for (unsigned i = 0; i < def->bond_params.arp_ip_targets->len; ++i) {
if (i > 0)
g_string_append_printf(tmp_val, ",");
g_string_append_printf(tmp_val, "%s", g_array_index(def->bond_params.arp_ip_targets, char*, i));
}
g_key_file_set_string(kf, "bond", "arp_ip_target", tmp_val->str);
g_string_free(tmp_val, TRUE);
}
if (def->bond_params.arp_validate)
g_key_file_set_string(kf, "bond", "arp_validate", def->bond_params.arp_validate);
if (def->bond_params.arp_all_targets)
g_key_file_set_string(kf, "bond", "arp_all_targets", def->bond_params.arp_all_targets);
if (def->bond_params.up_delay)
g_key_file_set_string(kf, "bond", "updelay", def->bond_params.up_delay);
if (def->bond_params.down_delay)
g_key_file_set_string(kf, "bond", "downdelay", def->bond_params.down_delay);
if (def->bond_params.fail_over_mac_policy)
g_key_file_set_string(kf, "bond", "fail_over_mac", def->bond_params.fail_over_mac_policy);
if (def->bond_params.gratuitous_arp) {
g_key_file_set_integer(kf, "bond", "num_grat_arp", def->bond_params.gratuitous_arp);
/* Work around issue in NM where unset unsolicited_na will overwrite num_grat_arp:
* https://github.com/NetworkManager/NetworkManager/commit/42b0bef33c77a0921590b2697f077e8ea7805166 */
g_key_file_set_integer(kf, "bond", "num_unsol_na", def->bond_params.gratuitous_arp);
}
if (def->bond_params.packets_per_member)
g_key_file_set_integer(kf, "bond", "packets_per_slave", def->bond_params.packets_per_member); /* wokeignore:rule=slave */
if (def->bond_params.primary_reselect_policy)
g_key_file_set_string(kf, "bond", "primary_reselect", def->bond_params.primary_reselect_policy);
if (def->bond_params.resend_igmp)
g_key_file_set_integer(kf, "bond", "resend_igmp", def->bond_params.resend_igmp);
if (def->bond_params.learn_interval)
g_key_file_set_string(kf, "bond", "lp_interval", def->bond_params.learn_interval);
if (def->bond_params.primary_member)
g_key_file_set_string(kf, "bond", "primary", def->bond_params.primary_member);
}
static void
write_bridge_params_nm(const NetplanNetDefinition* def, GKeyFile *kf)
{
if (def->custom_bridging) {
if (def->bridge_params.ageing_time)
g_key_file_set_string(kf, "bridge", "ageing-time", def->bridge_params.ageing_time);
if (def->bridge_params.priority)
g_key_file_set_uint64(kf, "bridge", "priority", def->bridge_params.priority);
if (def->bridge_params.forward_delay)
g_key_file_set_string(kf, "bridge", "forward-delay", def->bridge_params.forward_delay);
if (def->bridge_params.hello_time)
g_key_file_set_string(kf, "bridge", "hello-time", def->bridge_params.hello_time);
if (def->bridge_params.max_age)
g_key_file_set_string(kf, "bridge", "max-age", def->bridge_params.max_age);
g_key_file_set_boolean(kf, "bridge", "stp", def->bridge_params.stp);
}
}
static gboolean
write_wireguard_params(const NetplanNetDefinition* def, GKeyFile *kf, GError** error)
{
/* The key was already validated via validate_tunnel_grammar(), but we need
* to differentiate between base64 key VS absolute path key-file. And a base64
* string could (theoretically) start with '/', so we use is_wireguard_key()
* as well to check for more specific characteristics (if needed). */
if (def->tunnel.private_key) {
if (def->tunnel.private_key[0] == '/' && !is_wireguard_key(def->tunnel.private_key)) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, "%s: private key needs to be base64 encoded when using the NM backend\n", def->id);
return FALSE;
} else
g_key_file_set_string(kf, "wireguard", "private-key", def->tunnel.private_key);
}
if (def->tunnel_private_key_flags != NETPLAN_KEY_FLAG_NONE)
g_key_file_set_uint64(kf, "wireguard", "private-key-flags", def->tunnel_private_key_flags);
if (def->tunnel.port)
g_key_file_set_uint64(kf, "wireguard", "listen-port", def->tunnel.port);
if (def->tunnel.fwmark)
g_key_file_set_uint64(kf, "wireguard", "fwmark", def->tunnel.fwmark);
if (def->wireguard_peers) {
for (guint i = 0; i < def->wireguard_peers->len; i++) {
NetplanWireguardPeer *peer = g_array_index (def->wireguard_peers, NetplanWireguardPeer*, i);
g_assert(peer->public_key);
g_autofree gchar* tmp_group = g_strdup_printf("wireguard-peer.%s", peer->public_key);
if (peer->keepalive)
g_key_file_set_integer(kf, tmp_group, "persistent-keepalive", peer->keepalive);
if (peer->endpoint)
g_key_file_set_string(kf, tmp_group, "endpoint", peer->endpoint);
/* The key was already validated via validate_tunnel_grammar(), but we need
* to differentiate between base64 key VS absolute path key-file. And a base64
* string could (theoretically) start with '/', so we use is_wireguard_key()
* as well to check for more specific characteristics (if needed). */
if (peer->preshared_key) {
if (peer->preshared_key[0] == '/' && !is_wireguard_key(peer->preshared_key)) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, "%s: shared key needs to be base64 encoded when using the NM backend\n", def->id);
return FALSE;
} else {
g_key_file_set_value(kf, tmp_group, "preshared-key", peer->preshared_key);
g_key_file_set_uint64(kf, tmp_group, "preshared-key-flags", 0);
}
}
if (peer->allowed_ips && peer->allowed_ips->len > 0) {
const gchar* list[peer->allowed_ips->len];
for (guint j = 0; j < peer->allowed_ips->len; ++j)
list[j] = g_array_index(peer->allowed_ips, char*, j);
g_key_file_set_string_list(kf, tmp_group, "allowed-ips", list, peer->allowed_ips->len);
}
}
}
return TRUE;
}
static void
write_tunnel_params(const NetplanNetDefinition* def, GKeyFile *kf)
{
g_key_file_set_integer(kf, "ip-tunnel", "mode", def->tunnel.mode);
if (def->tunnel.local_ip)
g_key_file_set_string(kf, "ip-tunnel", "local", def->tunnel.local_ip);
g_key_file_set_string(kf, "ip-tunnel", "remote", def->tunnel.remote_ip);
if (def->tunnel_ttl)
g_key_file_set_uint64(kf, "ip-tunnel", "ttl", def->tunnel_ttl);
if (def->tunnel.input_key)
g_key_file_set_string(kf, "ip-tunnel", "input-key", def->tunnel.input_key);
if (def->tunnel.output_key)
g_key_file_set_string(kf, "ip-tunnel", "output-key", def->tunnel.output_key);
}
static void
write_dot1x_auth_parameters(const NetplanAuthenticationSettings* auth, GKeyFile *kf)
{
switch (auth->eap_method) {
case NETPLAN_AUTH_EAP_TLS:
g_key_file_set_string(kf, "802-1x", "eap", "tls");
break;
case NETPLAN_AUTH_EAP_PEAP:
g_key_file_set_string(kf, "802-1x", "eap", "peap");
break;
case NETPLAN_AUTH_EAP_TTLS:
g_key_file_set_string(kf, "802-1x", "eap", "ttls");
break;
case NETPLAN_AUTH_EAP_LEAP:
g_key_file_set_string(kf, "802-1x", "eap", "leap");
break;
case NETPLAN_AUTH_EAP_PWD:
g_key_file_set_string(kf, "802-1x", "eap", "pwd");
break;
default: break; // LCOV_EXCL_LINE
}
if (auth->identity)
g_key_file_set_string(kf, "802-1x", "identity", auth->identity);
if (auth->anonymous_identity)
g_key_file_set_string(kf, "802-1x", "anonymous-identity", auth->anonymous_identity);
/* auth->password might contain the PSK if it was defined inside the auth key in the YAML file.
* We only write auth-password in [802-1x].password if it's not a PSK used by either PSK or SAE
* or if an EAP method was defined.
*/
if (auth->password && (!_is_auth_key_management_psk(auth) || auth->eap_method != NETPLAN_AUTH_EAP_NONE))
g_key_file_set_string(kf, "802-1x", "password", auth->password);
if (auth->ca_certificate)
g_key_file_set_string(kf, "802-1x", "ca-cert", auth->ca_certificate);
if (auth->client_certificate)
g_key_file_set_string(kf, "802-1x", "client-cert", auth->client_certificate);
if (auth->client_key)
g_key_file_set_string(kf, "802-1x", "private-key", auth->client_key);
if (auth->client_key_password)
g_key_file_set_string(kf, "802-1x", "private-key-password", auth->client_key_password);
if (auth->phase2_auth)
g_key_file_set_string(kf, "802-1x", "phase2-auth", auth->phase2_auth);
}
static void
write_wifi_auth_parameters(const NetplanAuthenticationSettings* auth, GKeyFile *kf)
{
switch (auth->key_management) {
case NETPLAN_AUTH_KEY_MANAGEMENT_NONE:
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK:
g_key_file_set_string(kf, "wifi-security", "key-mgmt", "wpa-psk");
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP:
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSHA256:
/* NM uses "wpa-eap" to enable both EAP and EAP-SHA256 */
g_key_file_set_string(kf, "wifi-security", "key-mgmt", "wpa-eap");
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSUITE_B_192:
g_key_file_set_string(kf, "wifi-security", "key-mgmt", "wpa-eap-suite-b-192");
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_SAE:
g_key_file_set_string(kf, "wifi-security", "key-mgmt", "sae");
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_8021X:
g_key_file_set_string(kf, "wifi-security", "key-mgmt", "ieee8021x");
break;
default: break; // LCOV_EXCL_LINE
}
if (auth->key_management != NETPLAN_AUTH_KEY_MANAGEMENT_8021X) {
switch (auth->pmf_mode) {
case NETPLAN_AUTH_PMF_MODE_NONE:
case NETPLAN_AUTH_PMF_MODE_DISABLED:
break;
case NETPLAN_AUTH_PMF_MODE_OPTIONAL:
g_key_file_set_integer(kf, "wifi-security", "pmf", 2);
break;
case NETPLAN_AUTH_PMF_MODE_REQUIRED:
g_key_file_set_integer(kf, "wifi-security", "pmf", 3);
break;
}
}
write_dot1x_auth_parameters(auth, kf);
if (auth->psk)
g_key_file_set_string(kf, "wifi-security", "psk", auth->psk);
else if (auth->password && _is_auth_key_management_psk(auth))
g_key_file_set_string(kf, "wifi-security", "psk", auth->password);
}
static void
maybe_generate_uuid(const NetplanNetDefinition* def)
{
if (uuid_is_null(def->uuid))
uuid_generate((unsigned char*)def->uuid);
}
static void
write_vxlan_parameters(const NetplanNetDefinition* def, GKeyFile* kf)
{
g_assert(def->vxlan);
char uuidstr[37];
if (def->vxlan->ageing)
g_key_file_set_uint64(kf, "vxlan", "ageing", def->vxlan->ageing);
if (def->tunnel.port)
g_key_file_set_uint64(kf, "vxlan", "destination-port", def->tunnel.port);
if (def->vxlan->vni)
g_key_file_set_uint64(kf, "vxlan", "id", def->vxlan->vni);
if (def->vxlan->mac_learning != NETPLAN_TRISTATE_UNSET)
g_key_file_set_boolean(kf, "vxlan", "learning", def->vxlan->mac_learning);
if (def->vxlan->limit)
g_key_file_set_uint64(kf, "vxlan", "limit", def->vxlan->limit);
if (def->tunnel.local_ip)
g_key_file_set_string(kf, "vxlan", "local", def->tunnel.local_ip);
if (def->tunnel.remote_ip)
g_key_file_set_string(kf, "vxlan", "remote", def->tunnel.remote_ip);
if (def->vxlan->arp_proxy != NETPLAN_TRISTATE_UNSET)
g_key_file_set_boolean(kf, "vxlan", "proxy", def->vxlan->arp_proxy);
if (def->vxlan->notifications) {
if (def->vxlan->notifications & NETPLAN_VXLAN_NOTIFICATION_L2_MISS)
g_key_file_set_boolean(kf, "vxlan", "l2-miss", TRUE);
if (def->vxlan->notifications & NETPLAN_VXLAN_NOTIFICATION_L3_MISS)
g_key_file_set_boolean(kf, "vxlan", "l3-miss", TRUE);
}
if (def->vxlan->source_port_min && def->vxlan->source_port_max) {
g_key_file_set_uint64(kf, "vxlan", "source-port-min", def->vxlan->source_port_min);
g_key_file_set_uint64(kf, "vxlan", "source-port-max", def->vxlan->source_port_max);
}
if (def->vxlan->tos)
g_key_file_set_uint64(kf, "vxlan", "tos", def->vxlan->tos);
if (def->tunnel_ttl)
g_key_file_set_uint64(kf, "vxlan", "ttl", def->tunnel_ttl);
if (def->vxlan->short_circuit != NETPLAN_TRISTATE_UNSET)
g_key_file_set_boolean(kf, "vxlan", "rsc", def->vxlan->short_circuit);
if (def->vxlan->link) {
if (def->vxlan->link->has_match) {
/* we need to refer to the parent's UUID as we don't have an
* interface name with match: */
maybe_generate_uuid(def->vxlan->link);
uuid_unparse(def->vxlan->link->uuid, uuidstr);
g_key_file_set_string(kf, "vxlan", "parent", uuidstr);
} else {
/* if we have an interface name, use that as parent */
g_key_file_set_string(kf, "vxlan", "parent", def->vxlan->link->id);
}
}
if (def->vxlan->checksums || def->vxlan->extensions || def->vxlan->flow_label != G_MAXUINT || def->vxlan->do_not_fragment != NETPLAN_TRISTATE_UNSET)
g_warning("%s: checksums/extensions/flow-lable/do-not-fragment are not supported by NetworkManager\n", def->id);
}
/**
* Special handling for passthrough mode: read key-value pairs from
* "backend_settings.passthrough" and inject them into the keyfile as-is.
*/
static void
write_fallback_key_value(GQuark key_id, gpointer value, gpointer user_data)
{
GKeyFile *kf = user_data;
gchar* val = value;
/* Group name may contain dots, but key name may not.
* The "tc" group is a special case, where it is the other way around, e.g.:
* tc->qdisc.root
* tc->tfilter.ffff: */
const gchar* key = g_quark_to_string(key_id);
gchar **group_key = g_strsplit(key, ".", -1);
guint len = g_strv_length(group_key);
g_autofree gchar* old_key = NULL;
gboolean has_key = FALSE;
g_autofree gchar* k = NULL;
g_autofree gchar* group = NULL;
if (!g_strcmp0(group_key[0], "tc") && len > 2) {
k = g_strconcat(group_key[1], ".", group_key[2], NULL);
group = g_strdup(group_key[0]);
} else {
k = group_key[len-1];
group_key[len-1] = NULL; //remove key from array
group = g_strjoinv(".", group_key); //re-combine group parts
}
has_key = g_key_file_has_key(kf, group, k, NULL);
old_key = g_key_file_get_string(kf, group, k, NULL);
g_key_file_set_string(kf, group, k, val);
/* delete the placeholder key, if this was just an empty group */
if (!g_strcmp0(k, NETPLAN_NM_EMPTY_GROUP))
g_key_file_remove_key(kf, group, k, NULL);
/* handle differing defaults:
* ipv6.ip6-privacy is "-1 (unknown)" by default in NM, it is "0 (off)" in netplan */
else if (g_strcmp0(key, "ipv6.ip6-privacy") == 0 && g_strcmp0(val, "-1") == 0) {
g_debug("NetworkManager: default override: clearing %s.%s", group, k);
g_key_file_remove_key(kf, group, k, NULL);
} else if (!has_key) {
g_debug("NetworkManager: passing through fallback key: %s.%s=%s", group, k, val);
g_key_file_set_comment(kf, group, k, "Netplan: passthrough setting", NULL);
} else if (!!g_strcmp0(val, old_key)) {
g_debug("NetworkManager: fallback override: %s.%s=%s", group, k, val);
g_key_file_set_comment(kf, group, k, "Netplan: passthrough override", NULL);
}
g_strfreev(group_key);
}
/**
* Generate NetworkManager configuration in @rootdir/run/NetworkManager/ for a
* particular NetplanNetDefinition and NetplanWifiAccessPoint, as NM requires a separate
* connection file for each SSID.
* @def: The NetplanNetDefinition for which to create a connection
* @rootdir: If not %NULL, generate configuration in this root directory
* (useful for testing).
* @ap: The access point for which to create a connection. Must be %NULL for
* non-wifi types.
*/
static gboolean
write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, const NetplanWifiAccessPoint* ap, GError** error)
{
g_autoptr(GKeyFile) kf = NULL;
g_autofree gchar* conf_path = NULL;
g_autofree gchar* full_path = NULL;
g_autofree gchar* nm_run_path = NULL;
g_autofree gchar* nd_nm_id = NULL;
const gchar* nm_type = NULL;
gchar* tmp_key = NULL;
mode_t orig_umask;
char uuidstr[37];
const char *match_interface_name = NULL;
if (def->type == NETPLAN_DEF_TYPE_WIFI)
g_assert(ap);
else
g_assert(ap == NULL);
if (def->type == NETPLAN_DEF_TYPE_VLAN && def->sriov_vlan_filter) {
g_debug("%s is defined as a hardware SR-IOV filtered VLAN, postponing creation", def->id);
return TRUE;
}
kf = g_key_file_new();
if (ap && ap->backend_settings.name)
g_key_file_set_string(kf, "connection", "id", ap->backend_settings.name);
else if (def->backend_settings.name)
g_key_file_set_string(kf, "connection", "id", def->backend_settings.name);
else {
/* Auto-generate a name for the connection profile, if not specified */
if (ap)
nd_nm_id = g_strdup_printf("netplan-%s-%s", def->id, ap->ssid);
else
nd_nm_id = g_strdup_printf("netplan-%s", def->id);
g_key_file_set_string(kf, "connection", "id", nd_nm_id);
}
nm_type = type_str(def);
if (nm_type && def->type != NETPLAN_DEF_TYPE_NM)
g_key_file_set_string(kf, "connection", "type", nm_type);
if (ap && ap->backend_settings.uuid)
g_key_file_set_string(kf, "connection", "uuid", ap->backend_settings.uuid);
else if (def->backend_settings.uuid)
g_key_file_set_string(kf, "connection", "uuid", def->backend_settings.uuid);
/* VLAN/VXLAN devices refer to us as their parent; if our ID is not a name
* but we have matches, parent= must be the connection UUID, so put it into
* the connection */
if ((def->has_vlans || def->has_vxlans) && def->has_match) {
maybe_generate_uuid(def);
uuid_unparse(def->uuid, uuidstr);
g_key_file_set_string(kf, "connection", "uuid", uuidstr);
}
if (def->activation_mode) {
/* XXX: For now NetworkManager only supports the "manual" activation
* mode */
if (!!g_strcmp0(def->activation_mode, "manual")) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: NetworkManager definitions do not support activation-mode %s\n", def->id, def->activation_mode);
return FALSE;
}
/* "manual" */
g_key_file_set_boolean(kf, "connection", "autoconnect", FALSE);
}
if (def->type < NETPLAN_DEF_TYPE_VIRTUAL) {
/* physical (existing) devices use matching; driver matching is not
* supported, MAC matching is done below (different keyfile section),
* so only match names here */
if (def->set_name)
g_key_file_set_string(kf, "connection", "interface-name", def->set_name);
else if (!def->has_match)
g_key_file_set_string(kf, "connection", "interface-name", def->id);
else if (def->match.original_name) {
if (strpbrk(def->match.original_name, "*[]?"))
match_interface_name = def->match.original_name;
else
g_key_file_set_string(kf, "connection", "interface-name", def->match.original_name);
}
/* else matches on something other than the name, do not restrict interface-name */
} else {
/* virtual (created) devices set a name */
if (strnlen(def->id, IF_NAMESIZE) == IF_NAMESIZE)
g_debug("interface-name %s is too long. Ignoring.", def->id);
else
g_key_file_set_string(kf, "connection", "interface-name", def->id);
if (def->type == NETPLAN_DEF_TYPE_BRIDGE)
write_bridge_params_nm(def, kf);
if (def->type == NETPLAN_DEF_TYPE_VRF) {
g_key_file_set_uint64(kf, "vrf", "table", def->vrf_table);
if (!write_routes_nm(def, kf, AF_INET, error) || !write_routes_nm(def, kf, AF_INET6, error))
return FALSE;
}
if (def->type == NETPLAN_DEF_TYPE_VETH) {
g_key_file_set_string(kf, "veth", "peer", def->veth_peer_link->id);
}
}
if (def->type == NETPLAN_DEF_TYPE_MODEM) {
const char* modem_type = modem_is_gsm(def) ? "gsm" : "cdma";
/* Use NetworkManager's auto configuration feature if no APN, username, or password is specified */
if (def->modem_params.auto_config || (!def->modem_params.apn &&
!def->modem_params.username && !def->modem_params.password)) {
g_key_file_set_boolean(kf, modem_type, "auto-config", TRUE);
} else {
if (def->modem_params.apn)
g_key_file_set_string(kf, modem_type, "apn", def->modem_params.apn);
if (def->modem_params.password)
g_key_file_set_string(kf, modem_type, "password", def->modem_params.password);
if (def->modem_params.username)
g_key_file_set_string(kf, modem_type, "username", def->modem_params.username);
}
if (def->modem_params.device_id)
g_key_file_set_string(kf, modem_type, "device-id", def->modem_params.device_id);
if (def->mtubytes)
g_key_file_set_uint64(kf, modem_type, "mtu", def->mtubytes);
if (def->modem_params.network_id)
g_key_file_set_string(kf, modem_type, "network-id", def->modem_params.network_id);
if (def->modem_params.number)
g_key_file_set_string(kf, modem_type, "number", def->modem_params.number);
if (def->modem_params.pin)
g_key_file_set_string(kf, modem_type, "pin", def->modem_params.pin);
if (def->modem_params.sim_id)
g_key_file_set_string(kf, modem_type, "sim-id", def->modem_params.sim_id);
if (def->modem_params.sim_operator_id)
g_key_file_set_string(kf, modem_type, "sim-operator-id", def->modem_params.sim_operator_id);
}
if (def->bridge) {
g_key_file_set_string(kf, "connection", "slave-type", "bridge"); /* wokeignore:rule=slave */
g_key_file_set_string(kf, "connection", "master", def->bridge); /* wokeignore:rule=master */
if (def->bridge_params.path_cost)
g_key_file_set_uint64(kf, "bridge-port", "path-cost", def->bridge_params.path_cost);
if (def->bridge_params.port_priority)
g_key_file_set_uint64(kf, "bridge-port", "priority", def->bridge_params.port_priority);
}
if (def->bond) {
g_key_file_set_string(kf, "connection", "slave-type", "bond"); /* wokeignore:rule=slave */
g_key_file_set_string(kf, "connection", "master", def->bond); /* wokeignore:rule=master */
}
if (def->vrf_link) {
g_key_file_set_string(kf, "connection", "slave-type", "vrf"); /* wokeignore:rule=slave */
g_key_file_set_string(kf, "connection", "master", def->vrf_link->id); /* wokeignore:rule=master */
}
if (def->ipv6_mtubytes) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: NetworkManager definitions do not support ipv6-mtu\n", def->id);
return FALSE;
}
if (def->type < NETPLAN_DEF_TYPE_VIRTUAL) {
/* Avoid adding an [ethernet] section into the [gsm/cdma] description. */
if (g_strcmp0(nm_type, "gsm") != 0 || g_strcmp0(nm_type, "cdma") != 0) {
if (g_strcmp0(nm_type, "ethernet") == 0)
g_key_file_set_integer(kf, nm_type, "wake-on-lan", def->wake_on_lan ? 1 : 0);
if (!def->set_name && def->match.mac)
g_key_file_set_string(kf, nm_type, "mac-address", def->match.mac);
if (def->set_mac)
g_key_file_set_string(kf, nm_type, "cloned-mac-address", def->set_mac);
if (def->mtubytes)
g_key_file_set_uint64(kf, nm_type, "mtu", def->mtubytes);
if (def->wowlan && def->wowlan > NETPLAN_WIFI_WOWLAN_DEFAULT)
g_key_file_set_uint64(kf, nm_type, "wake-on-wlan", def->wowlan);
if (def->ib_mode != NETPLAN_IB_MODE_KERNEL)
g_key_file_set_string(kf, nm_type, "transport-mode", netplan_infiniband_mode_name(def->ib_mode));
}
} else {
if (def->set_mac)
g_key_file_set_string(kf, "ethernet", "cloned-mac-address", def->set_mac);
if (def->mtubytes)
g_key_file_set_uint64(kf, "ethernet", "mtu", def->mtubytes);
}
if (def->type == NETPLAN_DEF_TYPE_VLAN) {
g_assert(def->vlan_id < G_MAXUINT);
g_assert(def->vlan_link != NULL);
g_key_file_set_uint64(kf, "vlan", "id", def->vlan_id);
if (def->vlan_link->has_match) {
/* we need to refer to the parent's UUID as we don't have an
* interface name with match: */
maybe_generate_uuid(def->vlan_link);
uuid_unparse(def->vlan_link->uuid, uuidstr);
g_key_file_set_string(kf, "vlan", "parent", uuidstr);
} else {
/* if we have an interface name, use that as parent */
g_key_file_set_string(kf, "vlan", "parent", def->vlan_link->id);
}
}
if (def->type == NETPLAN_DEF_TYPE_BOND)
write_bond_parameters(def, kf);
if (def->type == NETPLAN_DEF_TYPE_TUNNEL) {
if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD) {
if (!write_wireguard_params(def, kf, error))
return FALSE;
} else if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_VXLAN) {
write_vxlan_parameters(def, kf);
} else
write_tunnel_params(def, kf);
}
if (match_interface_name) {
const gchar* list[1] = {match_interface_name};
g_key_file_set_string_list(kf, "match", "interface-name", list, 1);
}
if (ap && ap->mode == NETPLAN_WIFI_MODE_AP)
g_key_file_set_string(kf, "ipv4", "method", "shared");
else if (def->dhcp4)
g_key_file_set_string(kf, "ipv4", "method", "auto");
else if (def->ip4_addresses)
/* This requires adding at least one address (done below) */
g_key_file_set_string(kf, "ipv4", "method", "manual");
else if (def->type == NETPLAN_DEF_TYPE_TUNNEL)
/* sit tunnels will not start in link-local apparently */
g_key_file_set_string(kf, "ipv4", "method", "disabled");
else
/* Without any address, this is the only available mode */
g_key_file_set_string(kf, "ipv4", "method", "link-local");
if (def->ip4_addresses) {
for (unsigned i = 0; i < def->ip4_addresses->len; ++i) {
tmp_key = g_strdup_printf("address%i", i+1);
g_key_file_set_string(kf, "ipv4", tmp_key, g_array_index(def->ip4_addresses, char*, i));
g_free(tmp_key);
}
}
if (def->gateway4)
g_key_file_set_string(kf, "ipv4", "gateway", def->gateway4);
if (def->ip4_nameservers) {
const gchar* list[def->ip4_nameservers->len];
for (unsigned i = 0; i < def->ip4_nameservers->len; ++i)
list[i] = g_array_index(def->ip4_nameservers, char*, i);
g_key_file_set_string_list(kf, "ipv4", "dns", list, def->ip4_nameservers->len);
}
/* We can only write search domains and routes if we have an address */
if (def->ip4_addresses || def->dhcp4) {
write_search_domains(def, "ipv4", kf);
if (!write_routes_nm(def, kf, AF_INET, error))
return FALSE;
}
if (!def->dhcp4_overrides.use_routes) {
g_key_file_set_boolean(kf, "ipv4", "ignore-auto-routes", TRUE);
g_key_file_set_boolean(kf, "ipv4", "never-default", TRUE);
}
if (def->dhcp4 && def->dhcp4_overrides.metric != NETPLAN_METRIC_UNSPEC)
g_key_file_set_uint64(kf, "ipv4", "route-metric", def->dhcp4_overrides.metric);
if (def->dhcp6 || def->ip6_addresses || def->gateway6 || def->ip6_nameservers || def->ip6_addr_gen_mode) {
g_key_file_set_string(kf, "ipv6", "method", def->dhcp6 ? "auto" : "manual");
if (def->ip6_addresses) {
for (unsigned i = 0; i < def->ip6_addresses->len; ++i) {
tmp_key = g_strdup_printf("address%i", i+1);
g_key_file_set_string(kf, "ipv6", tmp_key, g_array_index(def->ip6_addresses, char*, i));
g_free(tmp_key);
}
}
if (def->ip6_addr_gen_token) {
/* Token implies EUI-64, i.e mode=0 */
g_key_file_set_integer(kf, "ipv6", "addr-gen-mode", 0);
g_key_file_set_string(kf, "ipv6", "token", def->ip6_addr_gen_token);
} else if (def->ip6_addr_gen_mode)
g_key_file_set_string(kf, "ipv6", "addr-gen-mode", addr_gen_mode_str(def->ip6_addr_gen_mode));
if (def->ip6_privacy)
g_key_file_set_integer(kf, "ipv6", "ip6-privacy", 2);
else
g_key_file_set_integer(kf, "ipv6", "ip6-privacy", 0);
if (def->gateway6)
g_key_file_set_string(kf, "ipv6", "gateway", def->gateway6);
if (def->ip6_nameservers) {
const gchar* list[def->ip6_nameservers->len];
for (unsigned i = 0; i < def->ip6_nameservers->len; ++i)
list[i] = g_array_index(def->ip6_nameservers, char*, i);
g_key_file_set_string_list(kf, "ipv6", "dns", list, def->ip6_nameservers->len);
}
/* nm-settings(5) specifies search-domain for both [ipv4] and [ipv6] --
* We need to specify it here for the IPv6-only case - see LP: #1786726 */
write_search_domains(def, "ipv6", kf);
/* We can only write valid routes if there is a DHCPv6 or static IPv6 address */
if (!write_routes_nm(def, kf, AF_INET6, error))
return FALSE;
if (!def->dhcp6_overrides.use_routes) {
g_key_file_set_boolean(kf, "ipv6", "ignore-auto-routes", TRUE);
g_key_file_set_boolean(kf, "ipv6", "never-default", TRUE);
}
if (def->dhcp6_overrides.metric != NETPLAN_METRIC_UNSPEC)
g_key_file_set_uint64(kf, "ipv6", "route-metric", def->dhcp6_overrides.metric);
}
else
g_key_file_set_string(kf, "ipv6", "method", "ignore");
if (def->backend_settings.passthrough) {
g_debug("NetworkManager: using keyfile passthrough mode");
/* Write all key-value pairs from the hashtable into the keyfile,
* potentially overriding existing values, if not fully supported. */
g_datalist_foreach((GData**)&def->backend_settings.passthrough, write_fallback_key_value, kf);
}
if (ap) {
g_autofree char* escaped_ssid = g_uri_escape_string(ap->ssid, NULL, TRUE);
/* TODO: make use of netplan_netdef_get_output_filename() */
conf_path = g_strjoin(NULL, "run/NetworkManager/system-connections/netplan-", def->id, "-", escaped_ssid, ".nmconnection", NULL);
g_key_file_set_string(kf, "wifi", "ssid", ap->ssid);
if (ap->mode < NETPLAN_WIFI_MODE_OTHER)
g_key_file_set_string(kf, "wifi", "mode", wifi_mode_str(ap->mode));
if (ap->bssid)
g_key_file_set_string(kf, "wifi", "bssid", ap->bssid);
if (ap->hidden)
g_key_file_set_boolean(kf, "wifi", "hidden", TRUE);
if (ap->band == NETPLAN_WIFI_BAND_5 || ap->band == NETPLAN_WIFI_BAND_24) {
g_key_file_set_string(kf, "wifi", "band", wifi_band_str(ap->band));
/* Channel is only unambiguous, if band is set. */
if (ap->channel) {
/* Validate WiFi channel */
if (ap->band == NETPLAN_WIFI_BAND_5)
wifi_get_freq5(ap->channel);
else
wifi_get_freq24(ap->channel);
g_key_file_set_uint64(kf, "wifi", "channel", ap->channel);
}
}
if (ap->has_auth) {
write_wifi_auth_parameters(&ap->auth, kf);
}
if (ap->backend_settings.passthrough) {
g_debug("NetworkManager: using AP keyfile passthrough mode");
/* Write all key-value pairs from the hashtable into the keyfile,
* potentially overriding existing values, if not fully supported.
* AP passthrough values have higher priority than ND passthrough,
* because they are more specific and bound to the current SSID's
* NM connection profile. */
g_datalist_foreach((GData**)&ap->backend_settings.passthrough, write_fallback_key_value, kf);
}
} else {
/* TODO: make use of netplan_netdef_get_output_filename() */
conf_path = g_strjoin(NULL, "run/NetworkManager/system-connections/netplan-", def->id, ".nmconnection", NULL);
if (def->has_auth) {
write_dot1x_auth_parameters(&def->auth, kf);
}
}
/* Create /run/NetworkManager/ with 755 permissions if the folder is missing.
* Letting the next invokation of safe_mkdir_p_dir do it would result in
* more restrictive access because of the call to umask. */
nm_run_path = g_strjoin(G_DIR_SEPARATOR_S, rootdir ?: "", "run/NetworkManager/", NULL);
if (!g_file_test(nm_run_path, G_FILE_TEST_EXISTS))
safe_mkdir_p_dir(nm_run_path);
full_path = g_strjoin(G_DIR_SEPARATOR_S, rootdir ?: "", conf_path, NULL);
/* NM connection files might contain secrets, and NM insists on tight permissions */
orig_umask = umask(077);
safe_mkdir_p_dir(full_path);
if (!g_key_file_save_to_file(kf, full_path, error))
return FALSE; // LCOV_EXCL_LINE
umask(orig_umask);
return TRUE;
}
/**
* Generate NetworkManager configuration in @rootdir/run/NetworkManager/ for a
* particular NetplanNetDefinition.
* @rootdir: If not %NULL, generate configuration in this root directory
* (useful for testing).
*/
gboolean
netplan_netdef_write_nm(
__unused const NetplanState* np_state,
const NetplanNetDefinition* netdef,
const char* rootdir,
gboolean* has_been_written,
GError** error)
{
gboolean no_error = TRUE;
/* Placeholder interfaces are not supposed to be rendered */
if (netdef->type == NETPLAN_DEF_TYPE_NM_PLACEHOLDER_)
return TRUE;
SET_OPT_OUT_PTR(has_been_written, FALSE);
if (netdef->backend != NETPLAN_BACKEND_NM) {
g_debug("NetworkManager: definition %s is not for us (backend %i)", netdef->id, netdef->backend);
return TRUE;
}
if (netdef->match.driver && !netdef->set_name) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: NetworkManager definitions do not support matching by driver\n", netdef->id);
return FALSE;
}
if (netdef->address_options) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: NetworkManager does not support address options\n", netdef->id);
return FALSE;
}
if (netdef->type == NETPLAN_DEF_TYPE_VETH) {
/*
* Final validation of veths that can't be fully done during parsing due to the
* order the interfaces are parsed.
*/
if (!validate_veth_pair(np_state, netdef, error))
return FALSE;
}
if (netdef->type == NETPLAN_DEF_TYPE_WIFI) {
GHashTableIter iter;
gpointer key;
const NetplanWifiAccessPoint* ap;
g_assert(netdef->access_points);
g_hash_table_iter_init(&iter, netdef->access_points);
while (g_hash_table_iter_next(&iter, &key, (gpointer) &ap) && no_error)
no_error = write_nm_conf_access_point(netdef, rootdir, ap, error);
} else {
g_assert(netdef->access_points == NULL);
no_error = write_nm_conf_access_point(netdef, rootdir, NULL, error);
}
SET_OPT_OUT_PTR(has_been_written, TRUE);
return no_error;
}
gboolean
netplan_state_finish_nm_write(
const NetplanState* np_state,
const char* rootdir,
__unused GError** error)
{
GString* udev_rules = g_string_new(NULL);
GString* nm_conf = g_string_new(NULL);
if (netplan_state_get_netdefs_size(np_state) == 0) {
g_string_free(udev_rules, TRUE);
g_string_free(nm_conf, TRUE);
return TRUE;
}
/* Set all devices not managed by us to unmanaged, so that NM does not
* auto-connect and interferes.
* Also, mark all devices managed by us explicitly, so it won't get in
* conflict with the system's udev rules that might ignore some devices
* in containers via usr/lib/udev/rules.d/85-nm-unmanaged-devices.rules */
GList* iter = np_state->netdefs_ordered;
while (iter) {
const NetplanNetDefinition* nd = iter->data;
const gchar* nm_type;
GString *tmp = NULL;
guint unmanaged = nd->backend == NETPLAN_BACKEND_NM ? 0 : 1;
/* Special case: manage or ignore any device of given type on empty "match: {}" stanza */
if (nd->has_match && !nd->match.driver && !nd->match.mac && !nd->match.original_name) {
nm_type = type_str(nd);
g_assert(nm_type);
g_string_append_printf(nm_conf, "[device-netplan.%s.%s]\nmatch-device=type:%s\n"
"managed=%d\n\n", netplan_def_type_name(nd->type),
nd->id, nm_type, !unmanaged);
}
/* Normal case: manage or ignore devices by specific udev rules */
else {
const gchar *prefix = "SUBSYSTEM==\"net\", ACTION==\"add|change|move\",";
const gchar *suffix = nd->backend == NETPLAN_BACKEND_NM ? " ENV{NM_UNMANAGED}=\"0\"\n" : " ENV{NM_UNMANAGED}=\"1\"\n";
g_string_append_printf(udev_rules, "# netplan: network.%s.%s (on NetworkManager %s)\n",
netplan_def_type_name(nd->type), nd->id,
unmanaged ? "deny-list" : "allow-list");
/* Match by explicit interface name, if possible */
if (nd->set_name) {
// simple case: explicit new interface name
g_string_append_printf(udev_rules, "%s ENV{ID_NET_NAME}==\"%s\",%s", prefix, nd->set_name, suffix);
} else if (!nd->has_match) {
// simple case: explicit netplan ID is interface name
g_string_append_printf(udev_rules, "%s ENV{ID_NET_NAME}==\"%s\",%s", prefix, nd->id, suffix);
}
/* Also, match by explicit (new) MAC, if available */
if (nd->set_mac) {
tmp = g_string_new(nd->set_mac);
g_string_append_printf(udev_rules, "%s ATTR{address}==\"%s\",%s", prefix, g_string_ascii_down(tmp)->str, suffix);
g_string_free(tmp, TRUE);
}
/* Finally, add a full match, using all rules & globs available
* from the "match" stanza (e.g. original_name/mac/drivers)
* This will match the "old" interface (i.e. original MAC and/or
* interface name) if it got changed */
if (nd->has_match && (nd->match.original_name || nd->match.mac || nd->match.driver)) {
// match on original name glob
// TODO: maybe support matching on multiple name globs in the future (like drivers)
g_string_append(udev_rules, prefix);
if (nd->match.original_name)
g_string_append_printf(udev_rules, " ENV{ID_NET_NAME}==\"%s\",", nd->match.original_name);
// match on (explicit) MAC address. Yes this would be unique on its own, but we
// keep it within the "full match" to make the logic more comprehensible.
if (nd->match.mac) {
tmp = g_string_new(nd->match.mac);
g_string_append_printf(udev_rules, " ATTR{address}==\"%s\",", g_string_ascii_down(tmp)->str);
g_string_free(tmp, TRUE);
}
// match on (multiple) driver globs
if (nd->match.driver) {
gchar *drivers = NULL;
if (strchr(nd->match.driver, '\t')) {
gchar **split = g_strsplit(nd->match.driver, "\t", -1);
drivers = g_strjoinv("|", split);
g_strfreev(split);
} else
drivers = g_strdup(nd->match.driver);
g_string_append_printf(udev_rules, " ENV{ID_NET_DRIVER}==\"%s\",", drivers);
g_free(drivers);
}
g_string_append(udev_rules, suffix);
}
}
iter = iter->next;
}
/* write generated NetworkManager drop-in config */
if (nm_conf->len > 0)
g_string_free_to_file(nm_conf, rootdir, "run/NetworkManager/conf.d/netplan.conf", NULL);
else
g_string_free(nm_conf, TRUE);
/* write generated udev rules */
if (udev_rules->len > 0)
g_string_free_to_file(udev_rules, rootdir, "run/udev/rules.d/90-netplan.rules", NULL);
else
g_string_free(udev_rules, TRUE);
return TRUE;
}
/**
* Clean up all generated configurations in @rootdir from previous runs.
*/
gboolean
netplan_nm_cleanup(const char* rootdir)
{
g_autofree char* confpath = g_strjoin(NULL, rootdir ?: "", "/run/NetworkManager/conf.d/netplan.conf", NULL);
g_autofree char* global_manage_path = g_strjoin(NULL, rootdir ?: "", "/run/NetworkManager/conf.d/10-globally-managed-devices.conf", NULL);
unlink(confpath);
unlink(global_manage_path);
unlink_glob(rootdir, "/run/NetworkManager/system-connections/netplan-*");
return TRUE;
}
netplan-0.107.1/src/nm.h 0000664 0000000 0000000 00000001756 14536110317 0014705 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2016 Canonical, Ltd.
* Author: Martin Pitt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#pragma once
#include "netplan.h"
NETPLAN_INTERNAL gboolean
netplan_netdef_write_nm(
const NetplanState* np_state,
const NetplanNetDefinition* netdef,
const char* rootdir,
gboolean* has_been_written,
GError** error);
NETPLAN_INTERNAL gboolean
netplan_nm_cleanup(const char* rootdir);
netplan-0.107.1/src/openvswitch.c 0000664 0000000 0000000 00000053221 14536110317 0016631 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2020 Canonical, Ltd.
* Author: Łukasz 'sil2100' Zemczak
* Lukas 'slyon' Märdian
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#include
#include
#include
#include
#include "openvswitch.h"
#include "networkd.h"
#include "parse.h"
#include "parse-globals.h"
#include "util.h"
#include "util-internal.h"
static gboolean
write_ovs_systemd_unit(const char* id, const GString* cmds, const char* rootdir, gboolean physical, gboolean cleanup, const char* dependency, GError** error)
{
g_autofree gchar* id_escaped = NULL;
g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/systemd-networkd.service.wants/netplan-ovs-", id, ".service", NULL);
g_autofree char* path = g_strjoin(NULL, "/run/systemd/system/netplan-ovs-", id, ".service", NULL);
GString* s = g_string_new("[Unit]\n");
g_string_append_printf(s, "Description=OpenVSwitch configuration for %s\n", id);
g_string_append(s, "DefaultDependencies=no\n");
/* run any ovs-netplan unit only after openvswitch-switch.service is ready */
g_string_append_printf(s, "Wants=ovsdb-server.service\n");
g_string_append_printf(s, "After=ovsdb-server.service\n");
if (physical) {
id_escaped = systemd_escape((char*) id);
g_string_append_printf(s, "Requires=sys-subsystem-net-devices-%s.device\n", id_escaped);
g_string_append_printf(s, "After=sys-subsystem-net-devices-%s.device\n", id_escaped);
}
if (!cleanup) {
g_string_append_printf(s, "After=netplan-ovs-cleanup.service\n");
} else {
/* The netplan-ovs-cleanup unit shall not run on systems where Open vSwitch is not installed. */
g_string_append(s, "ConditionFileIsExecutable=" OPENVSWITCH_OVS_VSCTL "\n");
}
g_string_append(s, "Before=network.target\nWants=network.target\n");
if (dependency) {
g_string_append_printf(s, "Requires=netplan-ovs-%s.service\n", dependency);
g_string_append_printf(s, "After=netplan-ovs-%s.service\n", dependency);
}
g_string_append(s, "\n[Service]\nType=oneshot\nTimeoutStartSec=10s\n");
g_string_append(s, cmds->str);
g_string_free_to_file(s, rootdir, path, NULL);
safe_mkdir_p_dir(link);
if (symlink(path, link) < 0 && errno != EEXIST) {
// LCOV_EXCL_START
g_set_error(error, NETPLAN_FILE_ERROR, errno, "failed to create enablement symlink: %m\n");
return FALSE;
// LCOV_EXCL_STOP
}
return TRUE;
}
#define append_systemd_cmd(s, command, ...) \
{ \
g_string_append(s, "ExecStart="); \
g_string_append_printf(s, command, __VA_ARGS__); \
g_string_append(s, "\n"); \
}
static char*
netplan_type_to_table_name(const NetplanDefType type)
{
switch (type) {
case NETPLAN_DEF_TYPE_BRIDGE:
return "Bridge";
case NETPLAN_DEF_TYPE_BOND:
case NETPLAN_DEF_TYPE_PORT:
return "Port";
default: /* For regular interfaces and others */
return "Interface";
}
}
static gboolean
netplan_type_is_physical(const NetplanDefType type)
{
switch (type) {
case NETPLAN_DEF_TYPE_ETHERNET:
// case NETPLAN_DEF_TYPE_WIFI:
// case NETPLAN_DEF_TYPE_MODEM:
return TRUE;
default:
return FALSE;
}
}
static void
write_ovs_tag_setting(const gchar* id, const char* type, const char* col, const char* key, const char* value, GString* cmds)
{
g_assert(col);
g_assert(value);
g_autofree char *clean_value = g_strdup(value);
/* Replace " " -> "," if value contains spaces */
if (strchr(value, ' ')) {
char **split = g_strsplit(value, " ", -1);
g_free(clean_value);
clean_value = g_strjoinv(",", split);
g_strfreev(split);
}
GString* s = g_string_new("external-ids:netplan/");
g_string_append_printf(s, "%s", col);
if (key)
g_string_append_printf(s, "/%s", key);
g_string_append_printf(s, "=%s", clean_value);
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set %s %s %s", type, id, s->str);
g_string_free(s, TRUE);
}
static void
write_ovs_additional_data(GHashTable *data, const char* type, const gchar* id, GString* cmds, const char* setting)
{
GHashTableIter iter;
gchar* key;
gchar* value;
g_hash_table_iter_init(&iter, data);
while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &value)) {
/* XXX: we need to check what happens when an invalid key=value pair
gets supplied here. We might want to handle this somehow. */
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set %s %s %s:%s=%s",
type, id, setting, key, value);
write_ovs_tag_setting(id, type, setting, key, value, cmds);
}
}
static void
setup_patch_port(GString* s, const NetplanNetDefinition* def)
{
/* Execute the setup commands to create an OVS patch port atomically within
* the same command where this virtual interface is created. Either as a
* Port+Interface of an OVS bridge or as a Interface of an OVS bond. This
* avoids delays in the PatchPort creation and thus potential races. */
g_assert(def->type == NETPLAN_DEF_TYPE_PORT);
g_string_append_printf(s, " -- set Interface %s type=patch options:peer=%s",
def->id, def->peer);
}
static char*
write_ovs_bond_interfaces(const NetplanState* np_state, const NetplanNetDefinition* def, GString* cmds, GError** error)
{
NetplanNetDefinition* tmp_nd;
GHashTableIter iter;
gchar* key;
guint i = 0;
g_autoptr(GString) s = NULL;
g_autoptr(GString) patch_ports = g_string_new("");
if (!def->bridge) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, "Bond %s needs to be a member of an OpenVSwitch bridge\n", def->id);
return NULL;
}
s = g_string_new(OPENVSWITCH_OVS_VSCTL " --may-exist add-bond");
g_string_append_printf(s, " %s %s", def->bridge, def->id);
g_hash_table_iter_init(&iter, np_state->netdefs);
while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &tmp_nd)) {
if (!g_strcmp0(def->id, tmp_nd->bond)) {
/* Append and count bond interfaces */
g_string_append_printf(s, " %s", tmp_nd->id);
i++;
if (tmp_nd->type == NETPLAN_DEF_TYPE_PORT)
setup_patch_port(patch_ports, tmp_nd);
}
}
if (i < 2) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, "Bond %s needs to have at least 2 member interfaces\n", def->id);
return NULL;
}
g_string_append(s, patch_ports->str);
append_systemd_cmd(cmds, s->str, def->bridge, def->id);
return def->bridge;
}
static void
write_ovs_tag_netplan(const gchar* id, const char* type, GString* cmds)
{
/* Mark this bridge/port/interface as created by netplan */
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set %s %s external-ids:netplan=true",
type, id);
}
static gboolean
write_ovs_bond_mode(const NetplanNetDefinition* def, GString* cmds, GError** error)
{
char* value = NULL;
/* OVS supports only "active-backup", "balance-tcp" and "balance-slb":
* http://www.openvswitch.org/support/dist-docs/ovs-vswitchd.conf.db.5.txt */
if (!strcmp(def->bond_params.mode, "active-backup") ||
!strcmp(def->bond_params.mode, "balance-tcp") ||
!strcmp(def->bond_params.mode, "balance-slb")) {
value = def->bond_params.mode;
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Port %s bond_mode=%s", def->id, value);
write_ovs_tag_setting(def->id, "Port", "bond_mode", NULL, value, cmds);
} else {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, "%s: bond mode '%s' not supported by Open vSwitch\n",
def->id, def->bond_params.mode);
return FALSE;
}
return TRUE;
}
static void
write_ovs_bridge_interfaces(const NetplanState* np_state, const NetplanNetDefinition* def, GString* cmds)
{
NetplanNetDefinition* tmp_nd;
GHashTableIter iter;
gchar* key;
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " --may-exist add-br %s", def->id);
g_hash_table_iter_init(&iter, np_state->netdefs);
while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &tmp_nd)) {
/* OVS bonds will connect to their OVS bridge and create the interface/port themselves */
if ((tmp_nd->type != NETPLAN_DEF_TYPE_BOND || tmp_nd->backend != NETPLAN_BACKEND_OVS)
&& !g_strcmp0(def->id, tmp_nd->bridge)) {
GString * patch_ports = g_string_new("");
if (tmp_nd->type == NETPLAN_DEF_TYPE_PORT)
setup_patch_port(patch_ports, tmp_nd);
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " --may-exist add-port %s %s%s",
def->id, tmp_nd->id, patch_ports->str);
g_string_free(patch_ports, TRUE);
}
}
}
static void
write_ovs_protocols(const NetplanOVSSettings* ovs_settings, const gchar* bridge, GString* cmds)
{
g_assert(bridge);
GString* s = g_string_new(g_array_index(ovs_settings->protocols, char*, 0));
for (unsigned i = 1; i < ovs_settings->protocols->len; ++i)
g_string_append_printf(s, ",%s", g_array_index(ovs_settings->protocols, char*, i));
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Bridge %s protocols=%s", bridge, s->str);
write_ovs_tag_setting(bridge, "Bridge", "protocols", NULL, s->str, cmds);
g_string_free(s, TRUE);
}
static gboolean
check_ovs_ssl(const NetplanOVSSettings* settings, gchar* target, gboolean* needs_ssl, GError** error)
{
/* Check if target needs ssl */
if (g_str_has_prefix(target, "ssl:") || g_str_has_prefix(target, "pssl:")) {
/* Check if SSL is configured in settings->ssl */
if (!settings->ssl.ca_certificate || !settings->ssl.client_certificate ||
!settings->ssl.client_key) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, "ERROR: Open vSwitch bridge controller target '%s' needs SSL configuration, but global 'openvswitch.ssl' settings are not set\n", target);
return FALSE;
}
*needs_ssl = TRUE;
return TRUE;
}
*needs_ssl = FALSE;
return TRUE;
}
static gboolean
write_ovs_bridge_controller_targets(const NetplanOVSSettings* settings, const NetplanOVSController* controller, const gchar* bridge, GString* cmds, GError** error)
{
gchar* target = g_array_index(controller->addresses, char*, 0);
GString* s = g_string_sized_new(0);
gboolean ret = TRUE;
gboolean needs_ssl = FALSE;
for (unsigned i = 0; i < controller->addresses->len; ++i) {
target = g_array_index(controller->addresses, char*, i);
if (!needs_ssl)
if (!check_ovs_ssl(settings, target, &needs_ssl, error)) {
ret = FALSE;
goto cleanup;
}
g_string_append_printf(s, "%s ", target);
}
g_string_erase(s, s->len-1, 1);
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set-controller %s %s", bridge, s->str);
write_ovs_tag_setting(bridge, "Bridge", "global", "set-controller", s->str, cmds);
cleanup:
g_string_free(s, TRUE);
return ret;
}
/**
* Generate the OpenVSwitch systemd units for configuration of the selected netdef
* @rootdir: If not %NULL, generate configuration in this root directory
* (useful for testing).
*/
gboolean
netplan_netdef_write_ovs(const NetplanState* np_state, const NetplanNetDefinition* def, const char* rootdir, gboolean* has_been_written, GError** error)
{
g_autoptr(GString) cmds = g_string_new(NULL);
gchar* dependency = NULL;
const char* type = netplan_type_to_table_name(def->type);
g_autofree char* base_config_path = NULL;
char* value = NULL;
const NetplanOVSSettings* settings = &np_state->ovs_settings;
SET_OPT_OUT_PTR(has_been_written, FALSE);
/* TODO: maybe dynamically query the ovs-vsctl tool path? */
/* For OVS specific settings, we expect the backend to be set to OVS.
* The OVS backend is implicitly set, if an interface contains an empty "openvswitch: {}"
* key, or an "openvswitch:" key, containing more than "external-ids" and/or "other-config". */
if (def->backend == NETPLAN_BACKEND_OVS) {
switch (def->type) {
case NETPLAN_DEF_TYPE_BOND:
dependency = write_ovs_bond_interfaces(np_state, def, cmds, error);
if (!dependency)
return FALSE;
write_ovs_tag_netplan(def->id, type, cmds);
/* Set LACP mode, default to "off" */
value = def->ovs_settings.lacp? def->ovs_settings.lacp : "off";
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Port %s lacp=%s", def->id, value);
write_ovs_tag_setting(def->id, type, "lacp", NULL, value, cmds);
if (def->bond_params.mode && !write_ovs_bond_mode(def, cmds, error))
return FALSE;
break;
case NETPLAN_DEF_TYPE_BRIDGE:
write_ovs_bridge_interfaces(np_state, def, cmds);
write_ovs_tag_netplan(def->id, type, cmds);
/* Set fail-mode, default to "standalone" */
value = def->ovs_settings.fail_mode? def->ovs_settings.fail_mode : "standalone";
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set-fail-mode %s %s", def->id, value);
write_ovs_tag_setting(def->id, type, "global", "set-fail-mode", value, cmds);
/* Enable/disable mcast-snooping */
value = def->ovs_settings.mcast_snooping? "true" : "false";
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Bridge %s mcast_snooping_enable=%s", def->id, value);
write_ovs_tag_setting(def->id, type, "mcast_snooping_enable", NULL, value, cmds);
/* Enable/disable rstp */
value = def->ovs_settings.rstp? "true" : "false";
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Bridge %s rstp_enable=%s", def->id, value);
write_ovs_tag_setting(def->id, type, "rstp_enable", NULL, value, cmds);
/* Set protocols */
if (def->ovs_settings.protocols && def->ovs_settings.protocols->len > 0)
write_ovs_protocols(&(def->ovs_settings), def->id, cmds);
else if (settings->protocols && settings->protocols->len > 0)
write_ovs_protocols(settings, def->id, cmds);
/* Set controller target addresses */
if (def->ovs_settings.controller.addresses && def->ovs_settings.controller.addresses->len > 0) {
if (!write_ovs_bridge_controller_targets(settings, &(def->ovs_settings.controller), def->id, cmds, error))
return FALSE;
/* Set controller connection mode, only applicable if at least one controller target address was set */
if (def->ovs_settings.controller.connection_mode) {
value = def->ovs_settings.controller.connection_mode;
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Controller %s connection-mode=%s", def->id, value);
write_ovs_tag_setting(def->id, "Controller", "connection-mode", NULL, value, cmds);
}
}
break;
case NETPLAN_DEF_TYPE_PORT:
g_assert(def->peer);
dependency = def->bridge?: def->bond;
if (!dependency) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, "%s: OpenVSwitch patch port needs to be assigned to a bridge/bond\n", def->id);
return FALSE;
}
/* There is no OVS Port which we could tag netplan=true if this
* patch port is assigned as an OVS bond interface. Tag the
* Interface instead, to clean it up from a bond. */
if (def->bond)
write_ovs_tag_netplan(def->id, "Interface", cmds);
else
write_ovs_tag_netplan(def->id, type, cmds);
break;
case NETPLAN_DEF_TYPE_VLAN:
g_assert(def->vlan_link);
dependency = def->vlan_link->id;
/* Create a fake VLAN bridge */
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " --may-exist add-br %s %s %i", def->id, def->vlan_link->id, def->vlan_id)
write_ovs_tag_netplan(def->id, type, cmds);
break;
default:
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, "%s: This device type is not supported with the OpenVSwitch backend\n", def->id);
return FALSE;
}
/* Try writing out a base config */
/* TODO: make use of netplan_netdef_get_output_filename() */
base_config_path = g_strjoin(NULL, "run/systemd/network/10-netplan-", def->id, NULL);
if (!netplan_netdef_write_network_file(np_state, def, rootdir, base_config_path, has_been_written, error))
return FALSE;
} else {
/* Other interfaces must be part of an OVS bridge or bond to carry additional data */
if ( (def->ovs_settings.external_ids && g_hash_table_size(def->ovs_settings.external_ids) > 0)
|| (def->ovs_settings.other_config && g_hash_table_size(def->ovs_settings.other_config) > 0)) {
dependency = def->bridge?: def->bond;
if (!dependency) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, "%s: Interface needs to be assigned to an OVS bridge/bond to carry external-ids/other-config\n", def->id);
return FALSE;
}
} else {
g_debug("Open vSwitch: definition %s is not for us (backend %i)", def->id, def->backend);
SET_OPT_OUT_PTR(has_been_written, FALSE);
return TRUE;
}
}
/* Set "external-ids" and "other-config" after NETPLAN_BACKEND_OVS interfaces, as bonds,
* bridges, etc. might just be created before.*/
/* Common OVS settings can be specified even for non-OVS interfaces */
if (def->ovs_settings.external_ids && g_hash_table_size(def->ovs_settings.external_ids) > 0) {
write_ovs_additional_data(def->ovs_settings.external_ids, type,
def->id, cmds, "external-ids");
}
if (def->ovs_settings.other_config && g_hash_table_size(def->ovs_settings.other_config) > 0) {
write_ovs_additional_data(def->ovs_settings.other_config, type,
def->id, cmds, "other-config");
}
/* If we need to configure anything for this netdef, write the required systemd unit */
gboolean ret = TRUE;
if (cmds->len > 0)
ret = write_ovs_systemd_unit(def->id, cmds, rootdir, netplan_type_is_physical(def->type), FALSE, dependency, error);
SET_OPT_OUT_PTR(has_been_written, TRUE);
return ret;
}
/**
* Finalize the OpenVSwitch configuration (global config)
*/
gboolean
netplan_state_finish_ovs_write(const NetplanState* np_state, const char* rootdir, GError** error)
{
const NetplanOVSSettings* settings = &np_state->ovs_settings;
GString* cmds = g_string_new(NULL);
/* Global external-ids and other-config settings */
if (settings->external_ids && g_hash_table_size(settings->external_ids) > 0)
write_ovs_additional_data(settings->external_ids, "open_vswitch",
".", cmds, "external-ids");
if (settings->other_config && g_hash_table_size(settings->other_config) > 0)
write_ovs_additional_data(settings->other_config, "open_vswitch",
".", cmds, "other-config");
if (settings->ssl.client_key && settings->ssl.client_certificate &&
settings->ssl.ca_certificate) {
GString* value = g_string_new(NULL);
g_string_printf(value, "%s %s %s",
settings->ssl.client_key,
settings->ssl.client_certificate,
settings->ssl.ca_certificate);
append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set-ssl %s", value->str);
write_ovs_tag_setting(".", "open_vswitch", "global", "set-ssl", value->str, cmds);
g_string_free(value, TRUE);
}
gboolean ret = TRUE;
if (cmds->len > 0)
ret = write_ovs_systemd_unit("global", cmds, rootdir, FALSE, FALSE, NULL, error);
g_string_free(cmds, TRUE);
if (!ret)
return FALSE; // LCOV_EXCL_LINE
/* Clear all netplan=true tagged ports/bonds and bridges, via 'netplan apply --only-ovs-cleanup' */
cmds = g_string_new(NULL);
append_systemd_cmd(cmds, SBINDIR "/netplan apply %s", "--only-ovs-cleanup");
ret = write_ovs_systemd_unit("cleanup", cmds, rootdir, FALSE, TRUE, NULL, error);
g_string_free(cmds, TRUE);
return ret;
}
/**
* Clean up all generated configurations in @rootdir from previous runs.
*/
gboolean
netplan_ovs_cleanup(const char* rootdir)
{
unlink_glob(rootdir, "/run/systemd/system/systemd-networkd.service.wants/netplan-ovs-*.service");
unlink_glob(rootdir, "/run/systemd/system/netplan-ovs-*.service");
return TRUE;
}
netplan-0.107.1/src/openvswitch.h 0000664 0000000 0000000 00000002001 14536110317 0016624 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2020 Canonical, Ltd.
* Author: Łukasz 'sil2100' Zemczak
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#pragma once
#include "netplan.h"
NETPLAN_INTERNAL gboolean
netplan_netdef_write_ovs(
const NetplanState* np_state,
const NetplanNetDefinition* netdef,
const char* rootdir,
gboolean* has_been_written,
GError** error);
NETPLAN_INTERNAL gboolean
netplan_ovs_cleanup(const char* rootdir);
netplan-0.107.1/src/parse-globals.h 0000664 0000000 0000000 00000002455 14536110317 0017023 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Canonical, Ltd.
* Author: Simon Chopin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#pragma once
#include
#include "types-internal.h"
/* Written/updated by parse_yaml(): char* id → net_definition.
*
* Since both netdefs and netdefs_ordered store pointers to the same elements,
* we consider that only netdefs_ordered is owner of this data. One should not
* free() objects obtained from netdefs, and proper care should be taken to remove
* any reference of an object in netdefs when destroying it from netdefs_ordered.
*/
extern GHashTable*
netdefs;
extern GList*
netdefs_ordered;
extern NetplanOVSSettings
ovs_settings_global;
extern NetplanBackend
global_backend;
extern NetplanParser
global_parser;
netplan-0.107.1/src/parse-nm.c 0000664 0000000 0000000 00000127317 14536110317 0016012 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Canonical, Ltd.
* Author: Lukas Märdian
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#include
#include
#include
#include "netplan.h"
#include "parse-nm.h"
#include "parse.h"
#include "util.h"
#include "types-internal.h"
#include "util-internal.h"
#include "validation.h"
/**
* NetworkManager writes the alias for '802-3-ethernet' (ethernet),
* '802-11-wireless' (wifi) and '802-11-wireless-security' (wifi-security)
* by default, so we only need to check for those. See:
* https://bugzilla.gnome.org/show_bug.cgi?id=696940
* https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/commit/c36200a225aefb2a3919618e75682646899b82c0
*/
static NetplanDefType
type_from_str(const char* type_str)
{
if (!g_strcmp0(type_str, "ethernet") || !g_strcmp0(type_str, "802-3-ethernet"))
return NETPLAN_DEF_TYPE_ETHERNET;
else if (!g_strcmp0(type_str, "wifi") || !g_strcmp0(type_str, "802-11-wireless"))
return NETPLAN_DEF_TYPE_WIFI;
else if (!g_strcmp0(type_str, "gsm") || !g_strcmp0(type_str, "cdma"))
return NETPLAN_DEF_TYPE_MODEM;
else if (!g_strcmp0(type_str, "bridge"))
return NETPLAN_DEF_TYPE_BRIDGE;
else if (!g_strcmp0(type_str, "bond"))
return NETPLAN_DEF_TYPE_BOND;
else if (!g_strcmp0(type_str, "dummy")) /* wokeignore:rule=dummy */
return NETPLAN_DEF_TYPE_DUMMY; /* wokeignore:rule=dummy */
else if (!g_strcmp0(type_str, "veth"))
return NETPLAN_DEF_TYPE_VETH;
else if (!g_strcmp0(type_str, "vlan"))
return NETPLAN_DEF_TYPE_VLAN;
else if (!g_strcmp0(type_str, "vrf"))
return NETPLAN_DEF_TYPE_VRF;
else if ( !g_strcmp0(type_str, "wireguard")
|| !g_strcmp0(type_str, "vxlan")
|| !g_strcmp0(type_str, "ip-tunnel"))
return NETPLAN_DEF_TYPE_TUNNEL;
/* Unsupported type, needs to be specified via passthrough */
return NETPLAN_DEF_TYPE_NM;
}
static NetplanWifiMode
ap_type_from_str(const char* type_str)
{
if (!g_strcmp0(type_str, "infrastructure"))
return NETPLAN_WIFI_MODE_INFRASTRUCTURE;
else if (!g_strcmp0(type_str, "ap"))
return NETPLAN_WIFI_MODE_AP;
else if (!g_strcmp0(type_str, "adhoc"))
return NETPLAN_WIFI_MODE_ADHOC;
/* Unsupported mode, like "mesh" */
return NETPLAN_WIFI_MODE_OTHER;
}
static NetplanTunnelMode
tunnel_mode_from_str(const char* type_str)
{
if (!g_strcmp0(type_str, "wireguard"))
return NETPLAN_TUNNEL_MODE_WIREGUARD;
else if (!g_strcmp0(type_str, "vxlan"))
return NETPLAN_TUNNEL_MODE_VXLAN;
return NETPLAN_TUNNEL_MODE_UNKNOWN;
}
static void
_kf_clear_key(GKeyFile* kf, const gchar* group, const gchar* key)
{
gsize len = 1;
g_key_file_remove_key(kf, group, key, NULL);
g_strfreev(g_key_file_get_keys(kf, group, &len, NULL));
/* clear group if this was the last key */
if (len == 0)
g_key_file_remove_group(kf, group, NULL);
}
static gboolean
kf_matches(GKeyFile* kf, const gchar* group, const gchar* key, const gchar* match)
{
g_autofree gchar *kf_value = g_key_file_get_string(kf, group, key, NULL);
return g_strcmp0(kf_value, match) == 0;
}
static void
set_true_on_match(GKeyFile* kf, const gchar* group, const gchar* key, const gchar* match, const void* dataptr)
{
g_assert(dataptr);
if (kf_matches(kf, group, key, match)) {
*((gboolean*) dataptr) = TRUE;
_kf_clear_key(kf, group, key);
}
}
static void
keyfile_handle_generic_bool(GKeyFile* kf, const gchar* group, const gchar* key, gboolean* dataptr)
{
g_assert(dataptr);
*dataptr = g_key_file_get_boolean(kf, group, key, NULL);
_kf_clear_key(kf, group, key);
}
static void
keyfile_handle_generic_str(GKeyFile* kf, const gchar* group, const gchar* key, char** dataptr)
{
g_assert(dataptr);
g_assert(!*dataptr);
*dataptr = g_key_file_get_string(kf, group, key, NULL);
if (*dataptr)
_kf_clear_key(kf, group, key);
}
static void
keyfile_handle_generic_uint(GKeyFile* kf, const gchar* group, const gchar* key, guint* dataptr, guint default_value)
{
g_assert(dataptr);
if (g_key_file_has_key(kf, group, key, NULL)) {
guint data = g_key_file_get_uint64(kf, group, key, NULL);
if (data != default_value)
*dataptr = data;
_kf_clear_key(kf, group, key);
}
}
static void
keyfile_handle_common(GKeyFile* kf, NetplanNetDefinition* nd, const gchar* group) {
keyfile_handle_generic_uint(kf, group, "mtu", &nd->mtubytes, NETPLAN_MTU_UNSPEC);
keyfile_handle_generic_str(kf, group, "mac-address", &nd->match.mac);
if (nd->match.mac)
nd->has_match = TRUE;
}
static void
keyfile_handle_bridge_uint(GKeyFile* kf, const gchar* key, NetplanNetDefinition* nd, char** dataptr) {
if (g_key_file_get_uint64(kf, "bridge", key, NULL)) {
nd->custom_bridging = TRUE;
*dataptr = g_strdup_printf("%"G_GUINT64_FORMAT, g_key_file_get_uint64(kf, "bridge", key, NULL));
_kf_clear_key(kf, "bridge", key);
}
}
static void
keyfile_handle_cloned_mac_address(GKeyFile *kf, NetplanNetDefinition* nd, const gchar* group)
{
g_autofree gchar* mac = g_key_file_get_string(kf, group, "cloned-mac-address", NULL);
if (!mac) return;
/* If the value of "cloned-mac-address" is one of the below we don't try to
* parse it and leave it in the passthrough section.
*/
if ( g_strcmp0(mac, "preserve")
&& g_strcmp0(mac, "permanent")
&& g_strcmp0(mac, "random")
&& g_strcmp0(mac, "stable")
) {
nd->set_mac = g_strdup(mac);
_kf_clear_key(kf, group, "cloned-mac-address");
}
}
static void
parse_addresses(GKeyFile* kf, const gchar* group, GArray** ip_arr)
{
g_assert(ip_arr);
if (kf_matches(kf, group, "method", "manual")) {
gboolean unhandled_data = FALSE;
gchar *key = NULL;
gchar *kf_value = NULL;
gchar **split = NULL;
for (unsigned i = 1;; ++i) {
key = g_strdup_printf("address%u", i);
kf_value = g_key_file_get_string(kf, group, key, NULL);
if (!kf_value) {
g_free(key);
break;
}
if (!*ip_arr)
*ip_arr = g_array_new(FALSE, FALSE, sizeof(char*));
split = g_strsplit(kf_value, ",", 2);
g_free(kf_value);
/* Append "address/prefix" */
if (split[0]) {
/* no need to free 's', this will stay in the netdef */
gchar* s = g_strdup(split[0]);
g_array_append_val(*ip_arr, s);
}
if (!split[1])
_kf_clear_key(kf, group, key);
else
/* XXX: how to handle additional values (like "gateway") in split[n]? */
unhandled_data = TRUE;
g_strfreev(split);
g_free(key);
}
/* clear keyfile once all data was handled */
if (!unhandled_data)
_kf_clear_key(kf, group, "method");
}
}
static void
parse_routes(GKeyFile* kf, const gchar* group, GArray** routes_arr)
{
g_assert(routes_arr);
NetplanIPRoute *route = NULL;
gchar **split = NULL;
for (unsigned i = 1;; ++i) {
gboolean unhandled_data = FALSE;
g_autofree gchar* key = g_strdup_printf("route%u", i);
g_autofree gchar* kf_value = g_key_file_get_string(kf, group, key, NULL);
g_autofree gchar* options_key = g_strdup_printf("route%u_options", i);
g_autofree gchar* options_kf_value = g_key_file_get_string(kf, group, options_key, NULL);
if (!kf_value)
break;
if (!*routes_arr)
*routes_arr = g_array_new(FALSE, TRUE, sizeof(NetplanIPRoute*));
route = g_new0(NetplanIPRoute, 1);
route->type = g_strdup("unicast");
route->family = -1; /* 0 is a valid family ID */
route->metric = NETPLAN_METRIC_UNSPEC; /* 0 is a valid metric */
g_debug("%s: adding new route (kf)", key);
if (g_strcmp0(group, "ipv4") == 0)
route->family = AF_INET;
else if (g_strcmp0(group, "ipv6") == 0)
route->family = AF_INET6;
split = g_strsplit(kf_value, ",", 3);
/* Append "to" (address/prefix) */
if (split[0])
route->to = g_strdup(split[0]); //no need to free, will stay in netdef
/* Append gateway/via IP */
if (split[0] && split[1] &&
g_strcmp0(split[1], get_unspecified_address(route->family)) != 0 &&
g_strcmp0(split[1], "") != 0) {
route->scope = g_strdup("global");
route->via = g_strdup(split[1]); //no need to free, will stay in netdef
} else {
/* If the gateway (via) is unspecified, it means that this route is
* only valid on the local network (see nm-keyfile.c ->
* read_one_ip_address_or_route()), e.g.:
* ip route add NETWORK dev DEV [metric METRIC] */
route->scope = g_strdup("link");
}
/* Append metric */
if (split[0] && split[1] && split[2] && strtoul(split[2], NULL, 10) != NETPLAN_METRIC_UNSPEC)
route->metric = strtoul(split[2], NULL, 10);
g_strfreev(split);
/* Parse route options */
if (options_kf_value) {
g_debug("%s: adding new route_options (kf)", options_key);
split = g_strsplit(options_kf_value, ",", -1);
for (unsigned i = 0; split[i]; ++i) {
g_debug("processing route_option: %s", split[i]);
gchar **kv = g_strsplit(split[i], "=", 2);
if (g_strcmp0(kv[0], "onlink") == 0)
route->onlink = (g_strcmp0(kv[1], "true") == 0);
else if (g_strcmp0(kv[0], "initrwnd") == 0)
route->advertised_receive_window = strtoul(kv[1], NULL, 10);
else if (g_strcmp0(kv[0], "initcwnd") == 0)
route->congestion_window = strtoul(kv[1], NULL, 10);
else if (g_strcmp0(kv[0], "mtu") == 0)
route->mtubytes = strtoul(kv[1], NULL, 10);
else if (g_strcmp0(kv[0], "table") == 0)
route->table = strtoul(kv[1], NULL, 10);
else if (g_strcmp0(kv[0], "src") == 0)
route->from = g_strdup(kv[1]); //no need to free, will stay in netdef
else
unhandled_data = TRUE;
g_strfreev(kv);
}
g_strfreev(split);
if (!unhandled_data)
_kf_clear_key(kf, group, options_key);
}
/* Add route to array, clear keyfile */
g_array_append_val(*routes_arr, route);
if (!unhandled_data)
_kf_clear_key(kf, group, key);
}
}
static void
parse_dhcp_overrides(GKeyFile* kf, const gchar* group, NetplanDHCPOverrides* dataptr)
{
g_assert(dataptr);
if ( g_key_file_get_boolean(kf, group, "ignore-auto-routes", NULL)
&& g_key_file_get_boolean(kf, group, "never-default", NULL)) {
(*dataptr).use_routes = FALSE;
_kf_clear_key(kf, group, "ignore-auto-routes");
_kf_clear_key(kf, group, "never-default");
}
keyfile_handle_generic_uint(kf, group, "route-metric", &(*dataptr).metric, NETPLAN_METRIC_UNSPEC);
}
/*
static void
parse_search_domains(GKeyFile* kf, const gchar* group, GArray** domains_arr)
{
// Keep "dns-search" as fallback/passthrough, as netplan cannot
// differentiate between ipv4.dns-search and ipv6.dns-search
g_assert(domains_arr);
gsize len = 0;
gchar **split = g_key_file_get_string_list(kf, group, "dns-search", &len, NULL);
if (split) {
if (len == 0) {
//do not clear "dns-search", keep as fallback
//_kf_clear_key(kf, group, "dns-search");
return;
}
if (!*domains_arr)
*domains_arr = g_array_new(FALSE, FALSE, sizeof(char*));
for(unsigned i = 0; split[i]; ++i) {
char* s = g_strdup(split[i]); //no need to free, will stay in netdef
g_array_append_val(*domains_arr, s);
}
//do not clear "dns-search", keep as fallback
//_kf_clear_key(kf, group, "dns-search");
g_strfreev(split);
}
}
*/
static void
parse_nameservers(GKeyFile* kf, const gchar* group, GArray** nameserver_arr)
{
g_assert(nameserver_arr);
gchar **split = g_key_file_get_string_list(kf, group, "dns", NULL, NULL);
if (split) {
if (!*nameserver_arr)
*nameserver_arr = g_array_new(FALSE, FALSE, sizeof(char*));
for(unsigned i = 0; split[i]; ++i) {
if (strlen(split[i]) > 0) {
gchar* s = g_strdup(split[i]); //no need to free, will stay in netdef
g_array_append_val(*nameserver_arr, s);
}
}
_kf_clear_key(kf, group, "dns");
g_strfreev(split);
}
}
static void
parse_dot1x_auth(GKeyFile* kf, NetplanAuthenticationSettings* auth)
{
g_assert(auth);
g_autofree gchar* method = g_key_file_get_string(kf, "802-1x", "eap", NULL);
if (method && g_strcmp0(method, "") != 0) {
gchar** split = g_strsplit(method, ";", 2);
gchar* first_method = split[0];
if (g_strcmp0(first_method, "tls") == 0) {
auth->eap_method = NETPLAN_AUTH_EAP_TLS;
} else if (g_strcmp0(first_method, "peap") == 0) {
auth->eap_method = NETPLAN_AUTH_EAP_PEAP;
} else if (g_strcmp0(first_method, "ttls") == 0) {
auth->eap_method = NETPLAN_AUTH_EAP_TTLS;
} else if (g_strcmp0(first_method, "leap") == 0) {
auth->eap_method = NETPLAN_AUTH_EAP_LEAP;
} else if (g_strcmp0(first_method, "pwd") == 0) {
auth->eap_method = NETPLAN_AUTH_EAP_PWD;
} else {
auth->eap_method = NETPLAN_AUTH_EAP_UNKNOWN;
}
/* If "method" (which is a list separated by ";") has more than one value,
* we keep the key so it will also be written as a passthrough key.
* That's required because Network Manager accepts multiple methods
* but Netplan accepts only one.
*
* TODO: eap_method needs to be fixed to store multiple methods.
*
* If at this point the eap_method is still UNKNOWN we also keep the property because
* it's probably a setting we still don't support.
*/
if (auth->eap_method != NETPLAN_AUTH_EAP_UNKNOWN && (split[1] == NULL || !g_strcmp0(split[1], "")))
_kf_clear_key(kf, "802-1x", "eap");
g_strfreev(split);
}
keyfile_handle_generic_str(kf, "802-1x", "identity", &auth->identity);
keyfile_handle_generic_str(kf, "802-1x", "anonymous-identity", &auth->anonymous_identity);
keyfile_handle_generic_str(kf, "802-1x", "password", &auth->password);
keyfile_handle_generic_str(kf, "802-1x", "ca-cert", &auth->ca_certificate);
keyfile_handle_generic_str(kf, "802-1x", "client-cert", &auth->client_certificate);
keyfile_handle_generic_str(kf, "802-1x", "private-key", &auth->client_key);
keyfile_handle_generic_str(kf, "802-1x", "private-key-password", &auth->client_key_password);
keyfile_handle_generic_str(kf, "802-1x", "phase2-auth", &auth->phase2_auth);
}
static void
parse_bond_arp_ip_targets(GKeyFile* kf, GArray **targets_arr)
{
g_assert(targets_arr);
g_autofree gchar *v = g_key_file_get_string(kf, "bond", "arp_ip_target", NULL);
if (v) {
gchar** split = g_strsplit(v, ",", -1);
for (unsigned i = 0; split[i]; ++i) {
if (!*targets_arr)
*targets_arr = g_array_new(FALSE, FALSE, sizeof(char *));
gchar *s = g_strdup(split[i]);
g_array_append_val(*targets_arr, s);
}
_kf_clear_key(kf, "bond", "arp_ip_target");
g_strfreev(split);
}
}
/* Read the key-value pairs from the keyfile and pass them through to a map */
static void
read_passthrough(GKeyFile* kf, GData** list)
{
gchar **groups = NULL;
gchar **keys = NULL;
gchar *group_key = NULL;
gchar *value = NULL;
gsize klen = 0;
gsize glen = 0;
if (!*list)
g_datalist_init(list);
groups = g_key_file_get_groups(kf, &glen);
if (groups) {
for (unsigned i = 0; i < glen; ++i) {
klen = 0;
keys = g_key_file_get_keys(kf, groups[i], &klen, NULL);
if (klen == 0) {
/* empty group */
g_datalist_set_data_full(list, g_strconcat(groups[i], ".", NETPLAN_NM_EMPTY_GROUP, NULL), g_strdup(""), g_free);
continue;
}
for (unsigned j = 0; j < klen; ++j) {
value = g_key_file_get_string(kf, groups[i], keys[j], NULL);
if (!value) {
// LCOV_EXCL_START
g_warning("netplan: Keyfile: cannot read value of %s.%s", groups[i], keys[j]);
continue;
// LCOV_EXCL_STOP
}
group_key = g_strconcat(groups[i], ".", keys[j], NULL);
g_datalist_set_data_full(list, group_key, value, g_free);
g_free(group_key);
}
g_strfreev(keys);
}
g_strfreev(groups);
}
}
/*
* Network Manager differentiates Wireguard (connection.type=wireguard),
* VXLAN (connection.type=vxlan) and all the other types of tunnels (connection.type=ip-tunnel).
*
* Each of these three classes have different requirements so we handle them separately
* in this function.
*/
static void
parse_tunnels(GKeyFile* kf, NetplanNetDefinition* nd)
{
/* Handle wireguard tunnel */
if (nd->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD) {
/* Reading the private key */
nd->tunnel.private_key = g_key_file_get_string(kf, "wireguard", "private-key", NULL);
_kf_clear_key(kf, "wireguard", "private-key");
/* Reading the listen port */
nd->tunnel.port = g_key_file_get_uint64(kf, "wireguard", "listen-port", NULL);
_kf_clear_key(kf, "wireguard", "listen-port");
nd->tunnel_private_key_flags = g_key_file_get_integer(kf, "wireguard", "private-key-flags", NULL);
_kf_clear_key(kf, "wireguard", "private-key-flags");
gchar** keyfile_groups = g_key_file_get_groups(kf, NULL);
/* Handling peers
* Network Manager creates a keyfile group for each Wireguard peer.
* The group name has the form [wireguard-peer.] so,
* in order to read the peer's public key we need to split up the group name
* and read its second component.
* */
for (int i = 0; keyfile_groups[i] != NULL; i++) {
gchar* group = keyfile_groups[i];
if (g_str_has_prefix(group, "wireguard-peer.")) {
gchar** peer_split = g_strsplit(group, ".", 2);
if (!is_wireguard_key(peer_split[1])) {
g_warning("Wireguard peer's name is malformed: %s", group);
g_strfreev(peer_split);
continue;
}
if (!nd->wireguard_peers)
nd->wireguard_peers = g_array_new(FALSE, FALSE, sizeof(NetplanWireguardPeer*));
NetplanWireguardPeer* wireguard_peer = g_new0(NetplanWireguardPeer, 1);
wireguard_peer->public_key = g_strdup(peer_split[1]);
g_strfreev(peer_split);
/* Handle allowed-ips */
gchar* allowed_ips_str = g_key_file_get_string(kf, group, "allowed-ips", NULL);
if (allowed_ips_str) {
wireguard_peer->allowed_ips = g_array_new(FALSE, FALSE, sizeof(NetplanAddressOptions*));
gchar** allowed_ips_split = g_strsplit(allowed_ips_str, ";", 0);
for (int i = 0; allowed_ips_split[i] != NULL; i++) {
gchar* ip = allowed_ips_split[i];
if (g_strcmp0(ip, "")) {
gchar* address = NULL;
/*
* NM doesn't care if the prefix was omitted.
* Even though the WG manual says it requires the prefix,
* if it's omitted in its config file it will default to /32
* so we should do the same here and append a /32 if it's not present,
* otherwise we will generate a YAML that will fail validation.
*/
if (!g_strrstr(ip, "/"))
address = g_strdup_printf("%s/32", ip);
else
address = g_strdup(ip);
g_array_append_val(wireguard_peer->allowed_ips, address);
}
}
g_free(allowed_ips_str);
g_strfreev(allowed_ips_split);
_kf_clear_key(kf, group, "allowed-ips");
}
/* Handle endpoint */
gchar* endpoint = g_key_file_get_string(kf, group, "endpoint", NULL);
if (endpoint && g_strcmp0(endpoint, "")) {
/* Only set the endpoint if it's not NULL nor an empty string */
wireguard_peer->endpoint = endpoint;
}
_kf_clear_key(kf, group, "endpoint");
g_array_append_val(nd->wireguard_peers, wireguard_peer);
}
}
g_strfreev(keyfile_groups);
} else if (nd->tunnel.mode == NETPLAN_TUNNEL_MODE_VXLAN) {
/* Handle vxlan tunnel */
nd->vxlan = g_new0(NetplanVxlan, 1);
reset_vxlan(nd->vxlan);
/* Reading the VXLAN ID*/
nd->vxlan->vni = g_key_file_get_integer(kf, "vxlan", "id", NULL);
_kf_clear_key(kf, "vxlan", "id");
nd->tunnel.local_ip = g_key_file_get_string(kf, "vxlan", "local", NULL);
_kf_clear_key(kf, "vxlan", "local");
nd->tunnel.remote_ip = g_key_file_get_string(kf, "vxlan", "remote", NULL);
_kf_clear_key(kf, "vxlan", "remote");
} else {
/* Handle all the other types of tunnel */
nd->tunnel.mode = g_key_file_get_integer(kf, "ip-tunnel", "mode", NULL);
/* We don't want to automatically accept new types of tunnels introduced by Network Manager */
if (nd->tunnel.mode >= NETPLAN_TUNNEL_MODE_NM_MAX) {
nd->tunnel.mode = NETPLAN_TUNNEL_MODE_UNKNOWN;
return;
}
_kf_clear_key(kf, "ip-tunnel", "mode");
nd->tunnel.local_ip = g_key_file_get_string(kf, "ip-tunnel", "local", NULL);
_kf_clear_key(kf, "ip-tunnel", "local");
nd->tunnel.remote_ip = g_key_file_get_string(kf, "ip-tunnel", "remote", NULL);
_kf_clear_key(kf, "ip-tunnel", "remote");
}
}
/**
* Parse keyfile into a NetplanNetDefinition struct
* @filename: full path to the NetworkManager keyfile
*/
gboolean
netplan_parser_load_keyfile(NetplanParser* npp, const char* filename, GError** error)
{
g_autofree gchar *nd_id = NULL;
g_autofree gchar *uuid = NULL;
g_autofree gchar *type = NULL;
g_autofree gchar* wifi_mode = NULL;
g_autofree gchar* ssid = NULL;
g_autofree gchar* netdef_id = NULL;
ssize_t netdef_id_size = 0;
gchar *tmp_str = NULL;
gint pmf = 0;
NetplanNetDefinition* nd = NULL;
NetplanWifiAccessPoint* ap = NULL;
g_autoptr(GKeyFile) kf = g_key_file_new();
NetplanDefType nd_type = NETPLAN_DEF_TYPE_NONE;
if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_NONE, error)) {
g_warning("netplan: cannot load keyfile");
return FALSE;
}
ssid = g_key_file_get_string(kf, "wifi", "ssid", NULL);
if (!ssid)
ssid = g_key_file_get_string(kf, "802-11-wireless", "ssid", NULL);
netdef_id = g_malloc0(strlen(filename));
netdef_id_size = netplan_get_id_from_nm_filepath(filename, ssid, netdef_id, strlen(filename));
uuid = g_key_file_get_string(kf, "connection", "uuid", NULL);
if (!uuid) {
const char* msg = "netplan: Keyfile: cannot find connection.uuid";
g_set_error(error, NETPLAN_VALIDATION_ERROR, NETPLAN_ERROR_CONFIG_GENERIC, "%s", msg);
g_warning("%s", msg);
return FALSE;
}
type = g_key_file_get_string(kf, "connection", "type", NULL);
if (!type) {
const char* msg = "netplan: Keyfile: cannot find connection.type";
g_set_error(error, NETPLAN_VALIDATION_ERROR, NETPLAN_ERROR_CONFIG_GENERIC, "%s", msg);
g_warning("%s", msg);
return FALSE;
}
nd_type = type_from_str(type);
tmp_str = g_key_file_get_string(kf, "connection", "interface-name", NULL);
/* Use previously existing netdef IDs, if available, to override connections
* Else: generate a "NM-" ID */
if (netdef_id_size > 0) {
nd_id = g_strdup(netdef_id);
if (g_strcmp0(netdef_id, tmp_str) == 0)
_kf_clear_key(kf, "connection", "interface-name");
} else if (tmp_str && nd_type >= NETPLAN_DEF_TYPE_VIRTUAL && nd_type < NETPLAN_DEF_TYPE_NM) {
/* netdef ID equals "interface-name" for virtual devices (bridge/bond/...) */
nd_id = g_strdup(tmp_str);
_kf_clear_key(kf, "connection", "interface-name");
} else
nd_id = g_strconcat("NM-", uuid, NULL);
g_free(tmp_str);
nd = netplan_netdef_new(npp, nd_id, nd_type, NETPLAN_BACKEND_NM);
/* Handle uuid & NM name/id */
nd->backend_settings.uuid = g_strdup(uuid);
_kf_clear_key(kf, "connection", "uuid");
nd->backend_settings.name = g_key_file_get_string(kf, "connection", "id", NULL);
if (nd->backend_settings.name)
_kf_clear_key(kf, "connection", "id");
if (nd_type == NETPLAN_DEF_TYPE_NM)
goto only_passthrough; //do not try to handle any keys for connections types unknown to netplan
/* Handle some differing NM/netplan defaults */
tmp_str = g_key_file_get_string(kf, "ipv6", "method", NULL);
if ( g_key_file_has_group(kf, "ipv6") && g_strcmp0(tmp_str, "ignore") != 0 &&
!g_key_file_has_key(kf, "ipv6", "ip6-privacy", NULL)) {
/* put NM's default into passthrough, as this is not currently supported by netplan */
g_key_file_set_integer(kf, "ipv6", "ip6-privacy", -1);
}
g_free(tmp_str);
/* Handle tunnels */
if (nd_type == NETPLAN_DEF_TYPE_TUNNEL) {
nd->tunnel.mode = tunnel_mode_from_str(type);
parse_tunnels(kf, nd);
}
/* Handle veths */
if (nd_type == NETPLAN_DEF_TYPE_VETH) {
g_autofree gchar* veth_peer = g_key_file_get_string(kf, "veth", "peer", NULL);
if (!veth_peer) {
g_warning("netplan: Keyfile: cannot find veth.peer");
return FALSE;
} else {
/*
* Generate a placeholder interface to be the VETH's peer.
* It's required because Network Manager allows the creation of
* VETHs connections with a non-existing peer.
*/
nd->veth_peer_link = netplan_netdef_new(npp, veth_peer, NETPLAN_DEF_TYPE_NM_PLACEHOLDER_, NETPLAN_BACKEND_NM);
_kf_clear_key(kf, "veth", "peer");
}
}
/* Handle VRFs */
if (nd_type == NETPLAN_DEF_TYPE_VRF) {
if (g_key_file_has_key(kf, "vrf", "table", NULL)) {
nd->vrf_table = g_key_file_get_uint64(kf, "vrf", "table", NULL);
_kf_clear_key(kf, "vrf", "table");
}
}
/* remove supported values from passthrough, which have been handled */
if ( nd_type == NETPLAN_DEF_TYPE_ETHERNET
|| nd_type == NETPLAN_DEF_TYPE_WIFI
|| nd_type == NETPLAN_DEF_TYPE_MODEM
|| nd_type == NETPLAN_DEF_TYPE_BRIDGE
|| nd_type == NETPLAN_DEF_TYPE_BOND
|| nd_type == NETPLAN_DEF_TYPE_DUMMY /* wokeignore:rule=dummy */
|| nd_type == NETPLAN_DEF_TYPE_VLAN
|| nd_type == NETPLAN_DEF_TYPE_VETH
|| nd_type == NETPLAN_DEF_TYPE_VRF
|| (nd_type == NETPLAN_DEF_TYPE_TUNNEL && nd->tunnel.mode != NETPLAN_TUNNEL_MODE_UNKNOWN))
_kf_clear_key(kf, "connection", "type");
/* Handle match: Netplan usually defines a connection per interface, while
* NM connection profiles are usually applied to any interface of matching
* type (like wifi/ethernet/...). */
if (nd->type < NETPLAN_DEF_TYPE_VIRTUAL) {
nd->match.original_name = g_key_file_get_string(kf, "connection", "interface-name", NULL);
if (nd->match.original_name)
_kf_clear_key(kf, "connection", "interface-name");
/* Set match, even if it is empty, so the NM renderer will not force
* the netdef ID as interface-name */
nd->has_match = TRUE;
}
/* DHCPv4/v6 */
set_true_on_match(kf, "ipv4", "method", "auto", &nd->dhcp4);
set_true_on_match(kf, "ipv6", "method", "auto", &nd->dhcp6);
parse_dhcp_overrides(kf, "ipv4", &nd->dhcp4_overrides);
parse_dhcp_overrides(kf, "ipv6", &nd->dhcp6_overrides);
/* Manual IPv4/6 addresses */
parse_addresses(kf, "ipv4", &nd->ip4_addresses);
parse_addresses(kf, "ipv6", &nd->ip6_addresses);
/* Default gateways */
keyfile_handle_generic_str(kf, "ipv4", "gateway", &nd->gateway4);
keyfile_handle_generic_str(kf, "ipv6", "gateway", &nd->gateway6);
/* Routes */
parse_routes(kf, "ipv4", &nd->routes);
parse_routes(kf, "ipv6", &nd->routes);
/* DNS: XXX: How to differentiate ip4/ip6 search_domains?
parse_search_domains(kf, "ipv4", &nd->search_domains);
parse_search_domains(kf, "ipv6", &nd->search_domains);
*/
parse_nameservers(kf, "ipv4", &nd->ip4_nameservers);
parse_nameservers(kf, "ipv6", &nd->ip6_nameservers);
/* IP6 addr-gen
* Different than suggested by the docs, NM stores 'addr-gen-mode' as string */
tmp_str = g_key_file_get_string(kf, "ipv6", "addr-gen-mode", NULL);
if (tmp_str) {
if (g_strcmp0(tmp_str, "stable-privacy") == 0) {
nd->ip6_addr_gen_mode = NETPLAN_ADDRGEN_STABLEPRIVACY;
_kf_clear_key(kf, "ipv6", "addr-gen-mode");
} else if (g_strcmp0(tmp_str, "eui64") == 0) {
nd->ip6_addr_gen_mode = NETPLAN_ADDRGEN_EUI64;
_kf_clear_key(kf, "ipv6", "addr-gen-mode");
}
}
g_free(tmp_str);
keyfile_handle_generic_str(kf, "ipv6", "token", &nd->ip6_addr_gen_token);
/* ip6-privacy is not fully supported, NM supports additional modes, like -1 or 1
* handle known modes, but keep any unsupported "ip6-privacy" value in passthrough */
if (g_key_file_has_group(kf, "ipv6")) {
if (g_key_file_has_key(kf, "ipv6", "ip6-privacy", NULL)) {
int ip6_privacy = g_key_file_get_integer(kf, "ipv6", "ip6-privacy", NULL);
if (ip6_privacy == 0) {
nd->ip6_privacy = FALSE;
_kf_clear_key(kf, "ipv6", "ip6-privacy");
} else if (ip6_privacy == 2) {
nd->ip6_privacy = TRUE;
_kf_clear_key(kf, "ipv6", "ip6-privacy");
}
}
}
/* Modem parameters
* NM differentiates between GSM and CDMA connections, while netplan
* combines them as "modems". We need to parse a basic set of parameters
* to enable the generator (in nm.c) to detect GSM vs CDMA connections,
* using its modem_is_gsm() util. */
keyfile_handle_generic_bool(kf, "gsm", "auto-config", &nd->modem_params.auto_config);
keyfile_handle_generic_str(kf, "gsm", "apn", &nd->modem_params.apn);
keyfile_handle_generic_str(kf, "gsm", "device-id", &nd->modem_params.device_id);
keyfile_handle_generic_str(kf, "gsm", "network-id", &nd->modem_params.network_id);
keyfile_handle_generic_str(kf, "gsm", "pin", &nd->modem_params.pin);
keyfile_handle_generic_str(kf, "gsm", "sim-id", &nd->modem_params.sim_id);
keyfile_handle_generic_str(kf, "gsm", "sim-operator-id", &nd->modem_params.sim_operator_id);
/* GSM & CDMA */
keyfile_handle_generic_uint(kf, "cdma", "mtu", &nd->mtubytes, NETPLAN_MTU_UNSPEC);
keyfile_handle_generic_uint(kf, "gsm", "mtu", &nd->mtubytes, NETPLAN_MTU_UNSPEC);
keyfile_handle_generic_str(kf, "gsm", "number", &nd->modem_params.number);
if (!nd->modem_params.number)
keyfile_handle_generic_str(kf, "cdma", "number", &nd->modem_params.number);
keyfile_handle_generic_str(kf, "gsm", "password", &nd->modem_params.password);
if (!nd->modem_params.password)
keyfile_handle_generic_str(kf, "cdma", "password", &nd->modem_params.password);
keyfile_handle_generic_str(kf, "gsm", "username", &nd->modem_params.username);
if (!nd->modem_params.username)
keyfile_handle_generic_str(kf, "cdma", "username", &nd->modem_params.username);
/* Ethernets */
if (g_key_file_has_group(kf, "ethernet")) {
/* wake-on-lan, do not clear passthrough as we do not fully support this setting */
if (!g_key_file_has_key(kf, "ethernet", "wake-on-lan", NULL)) {
/* apply the default only to actual ethernet devices */
if (nd_type == NETPLAN_DEF_TYPE_ETHERNET)
nd->wake_on_lan = TRUE; //NM's default is "1"
} else {
guint value = g_key_file_get_uint64(kf, "ethernet", "wake-on-lan", NULL);
//XXX: fix delta between options in NM (0x1, 0x2, 0x4, ...) and netplan (bool)
nd->wake_on_lan = value > 0; // netplan only knows about "off" or "on"
if (value == 0)
_kf_clear_key(kf, "ethernet", "wake-on-lan"); // value "off" is supported
}
keyfile_handle_common(kf, nd, "ethernet");
keyfile_handle_cloned_mac_address(kf, nd, "ethernet");
}
/* Wifis */
if (g_key_file_has_group(kf, "wifi")) {
if (g_key_file_get_uint64(kf, "wifi", "wake-on-wlan", NULL)) {
nd->wowlan = g_key_file_get_uint64(kf, "wifi", "wake-on-wlan", NULL);
_kf_clear_key(kf, "wifi", "wake-on-wlan");
} else {
nd->wowlan = NETPLAN_WIFI_WOWLAN_DEFAULT;
}
keyfile_handle_common(kf, nd, "wifi");
keyfile_handle_cloned_mac_address(kf, nd, "wifi");
}
/* Cleanup some implicit keys */
tmp_str = g_key_file_get_string(kf, "ipv6", "method", NULL);
if (tmp_str && g_strcmp0(tmp_str, "ignore") == 0 &&
!(nd->dhcp6 || nd->ip6_addresses || nd->gateway6 ||
nd->ip6_nameservers || nd->ip6_addr_gen_mode))
_kf_clear_key(kf, "ipv6", "method");
g_free(tmp_str);
tmp_str = g_key_file_get_string(kf, "ipv4", "method", NULL);
if (tmp_str && g_strcmp0(tmp_str, "link-local") == 0 &&
!(nd->dhcp4 || nd->ip4_addresses || nd->gateway4 ||
nd->ip4_nameservers))
_kf_clear_key(kf, "ipv4", "method");
g_free(tmp_str);
/* Handling VLANs */
if (nd_type == NETPLAN_DEF_TYPE_VLAN) {
keyfile_handle_generic_uint(kf, "vlan", "id", &nd->vlan_id, G_MAXUINT);
g_autofree gchar* parent = g_key_file_get_string(kf, "vlan", "parent", NULL);
if (parent) {
/*
* Generate a placeholder interface to be the VLAN's parent.
* It's required because Network Manager allows the creation of
* VLAN connections with non-existing parent interfaces.
*/
nd->vlan_link = netplan_netdef_new(npp, parent, NETPLAN_DEF_TYPE_NM_PLACEHOLDER_, NETPLAN_BACKEND_NM);
_kf_clear_key(kf, "vlan", "parent");
}
}
/* Bridge: XXX: find a way to parse the bridge-port.priority & bridge-port.path-cost values */
keyfile_handle_generic_uint(kf, "bridge", "priority", &nd->bridge_params.priority, 0);
if (nd->bridge_params.priority)
nd->custom_bridging = TRUE;
keyfile_handle_bridge_uint(kf, "ageing-time", nd, &nd->bridge_params.ageing_time);
keyfile_handle_bridge_uint(kf, "hello-time", nd, &nd->bridge_params.hello_time);
keyfile_handle_bridge_uint(kf, "forward-delay", nd, &nd->bridge_params.forward_delay);
keyfile_handle_bridge_uint(kf, "max-age", nd, &nd->bridge_params.max_age);
/* STP needs to be handled last, for its different default value in custom_bridging mode */
if (g_key_file_has_key(kf, "bridge", "stp", NULL)) {
nd->custom_bridging = TRUE;
keyfile_handle_generic_bool(kf, "bridge", "stp", &nd->bridge_params.stp);
} else if(nd->custom_bridging) {
nd->bridge_params.stp = TRUE; //set default value if not specified otherwise
}
/* Bonds */
keyfile_handle_generic_str(kf, "bond", "mode", &nd->bond_params.mode);
keyfile_handle_generic_str(kf, "bond", "lacp_rate", &nd->bond_params.lacp_rate);
keyfile_handle_generic_str(kf, "bond", "miimon", &nd->bond_params.monitor_interval);
keyfile_handle_generic_str(kf, "bond", "xmit_hash_policy", &nd->bond_params.transmit_hash_policy);
keyfile_handle_generic_str(kf, "bond", "ad_select", &nd->bond_params.selection_logic);
keyfile_handle_generic_str(kf, "bond", "arp_interval", &nd->bond_params.arp_interval);
keyfile_handle_generic_str(kf, "bond", "arp_validate", &nd->bond_params.arp_validate);
keyfile_handle_generic_str(kf, "bond", "arp_all_targets", &nd->bond_params.arp_all_targets);
keyfile_handle_generic_str(kf, "bond", "updelay", &nd->bond_params.up_delay);
keyfile_handle_generic_str(kf, "bond", "downdelay", &nd->bond_params.down_delay);
keyfile_handle_generic_str(kf, "bond", "fail_over_mac", &nd->bond_params.fail_over_mac_policy);
keyfile_handle_generic_str(kf, "bond", "primary_reselect", &nd->bond_params.primary_reselect_policy);
keyfile_handle_generic_str(kf, "bond", "lp_interval", &nd->bond_params.learn_interval);
keyfile_handle_generic_str(kf, "bond", "primary", &nd->bond_params.primary_member);
keyfile_handle_generic_uint(kf, "bond", "min_links", &nd->bond_params.min_links, 0);
keyfile_handle_generic_uint(kf, "bond", "resend_igmp", &nd->bond_params.resend_igmp, 0);
keyfile_handle_generic_uint(kf, "bond", "packets_per_slave", &nd->bond_params.packets_per_member, 0); /* wokeignore:rule=slave */
keyfile_handle_generic_uint(kf, "bond", "num_grat_arp", &nd->bond_params.gratuitous_arp, 0);
/* num_unsol_na might overwrite num_grat_arp, but we're fine if they are equal:
* https://github.com/NetworkManager/NetworkManager/commit/42b0bef33c77a0921590b2697f077e8ea7805166 */
if (g_key_file_get_uint64(kf, "bond", "num_unsol_na", NULL) == nd->bond_params.gratuitous_arp)
_kf_clear_key(kf, "bond", "num_unsol_na");
keyfile_handle_generic_bool(kf, "bond", "all_slaves_active", &nd->bond_params.all_members_active); /* wokeignore:rule=slave */
parse_bond_arp_ip_targets(kf, &nd->bond_params.arp_ip_targets);
/* Special handling for WiFi "access-points:" mapping */
if (nd->type == NETPLAN_DEF_TYPE_WIFI) {
ap = g_new0(NetplanWifiAccessPoint, 1);
ap->ssid = g_key_file_get_string(kf, "wifi", "ssid", NULL);
if (!ap->ssid) {
const char* msg = "netplan: Keyfile: cannot find SSID for WiFi connection";
g_set_error(error, NETPLAN_VALIDATION_ERROR, NETPLAN_ERROR_CONFIG_GENERIC, "%s", msg);
g_warning("%s", msg);
g_free(ap);
return FALSE;
} else
_kf_clear_key(kf, "wifi", "ssid");
wifi_mode = g_key_file_get_string(kf, "wifi", "mode", NULL);
if (wifi_mode) {
ap->mode = ap_type_from_str(wifi_mode);
if (ap->mode != NETPLAN_WIFI_MODE_OTHER)
_kf_clear_key(kf, "wifi", "mode");
}
tmp_str = g_key_file_get_string(kf, "ipv4", "method", NULL);
if (tmp_str && g_strcmp0(tmp_str, "shared") == 0) {
ap->mode = NETPLAN_WIFI_MODE_AP;
_kf_clear_key(kf, "ipv4", "method");
}
g_free(tmp_str);
keyfile_handle_generic_bool(kf, "wifi", "hidden", &ap->hidden);
keyfile_handle_generic_str(kf, "wifi", "bssid", &ap->bssid);
/* Wifi band & channel */
tmp_str = g_key_file_get_string(kf, "wifi", "band", NULL);
if (tmp_str && g_strcmp0(tmp_str, "a") == 0) {
ap->band = NETPLAN_WIFI_BAND_5;
_kf_clear_key(kf, "wifi", "band");
} else if (tmp_str && g_strcmp0(tmp_str, "bg") == 0) {
ap->band = NETPLAN_WIFI_BAND_24;
_kf_clear_key(kf, "wifi", "band");
}
g_free(tmp_str);
keyfile_handle_generic_uint(kf, "wifi", "channel", &ap->channel, 0);
/* Wifi security */
tmp_str = g_key_file_get_string(kf, "wifi-security", "key-mgmt", NULL);
if (tmp_str && g_strcmp0(tmp_str, "wpa-psk") == 0) {
ap->auth.key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK;
ap->has_auth = TRUE;
_kf_clear_key(kf, "wifi-security", "key-mgmt");
} else if (tmp_str && g_strcmp0(tmp_str, "wpa-eap") == 0) {
ap->auth.key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP;
ap->has_auth = TRUE;
_kf_clear_key(kf, "wifi-security", "key-mgmt");
} else if (tmp_str && g_strcmp0(tmp_str, "wpa-eap-suite-b-192") == 0) {
ap->auth.key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSUITE_B_192;
ap->has_auth = TRUE;
_kf_clear_key(kf, "wifi-security", "key-mgmt");
} else if (tmp_str && g_strcmp0(tmp_str, "sae") == 0) {
ap->auth.key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_SAE;
ap->has_auth = TRUE;
_kf_clear_key(kf, "wifi-security", "key-mgmt");
} else if (tmp_str && g_strcmp0(tmp_str, "ieee8021x") == 0) {
ap->auth.key_management = NETPLAN_AUTH_KEY_MANAGEMENT_8021X;
ap->has_auth = TRUE;
_kf_clear_key(kf, "wifi-security", "key-mgmt");
}
g_free(tmp_str);
pmf = g_key_file_get_integer(kf, "wifi-security", "pmf", NULL);
switch (pmf) {
case 2:
ap->auth.pmf_mode = NETPLAN_AUTH_PMF_MODE_OPTIONAL;
_kf_clear_key(kf, "wifi-security", "pmf");
/* If pmf is set to 2 (optional) and the key management is EAP
* we set it to EAPSHA256 so the correct method is emitted in the YAML
*/
if (ap->auth.key_management == NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP)
ap->auth.key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSHA256;
break;
case 3:
ap->auth.pmf_mode = NETPLAN_AUTH_PMF_MODE_REQUIRED;
_kf_clear_key(kf, "wifi-security", "pmf");
break;
default: break;
}
keyfile_handle_generic_str(kf, "wifi-security", "psk", &ap->auth.psk);
if (ap->auth.psk)
ap->has_auth = TRUE;
parse_dot1x_auth(kf, &ap->auth);
if (ap->auth.eap_method != NETPLAN_AUTH_EAP_NONE)
ap->has_auth = TRUE;
if (!nd->access_points)
nd->access_points = g_hash_table_new(g_str_hash, g_str_equal);
g_hash_table_insert(nd->access_points, ap->ssid, ap);
/* Last: handle passthrough for everything left in the keyfile
* Also, transfer backend_settings from netdef to AP */
ap->backend_settings.uuid = g_strdup(nd->backend_settings.uuid);
ap->backend_settings.name = g_strdup(nd->backend_settings.name);
/* No need to clear nm.uuid & nm.name from def->backend_settings,
* as we have only one AP. */
read_passthrough(kf, &ap->backend_settings.passthrough);
} else {
only_passthrough:
/* Last: handle passthrough for everything left in the keyfile */
read_passthrough(kf, &nd->backend_settings.passthrough);
}
/* validate definition-level conditions */
if (!npp->missing_id)
npp->missing_id = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
if (!validate_netdef_grammar(npp, nd, error))
return FALSE;
return TRUE;
}
netplan-0.107.1/src/parse.c 0000664 0000000 0000000 00000470134 14536110317 0015400 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2016-2023 Canonical, Ltd.
* Author: Martin Pitt
* Author: Lukas Märdian
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "parse.h"
#include "names.h"
#include "util-internal.h"
#include "error.h"
#include "validation.h"
#define NETPLAN_VERSION_MIN 2
#define NETPLAN_VERSION_MAX 3
/* convenience macro to put the offset of a NetplanNetDefinition field into "void* data" */
#define access_point_offset(field) GUINT_TO_POINTER(offsetof(NetplanWifiAccessPoint, field))
#define addr_option_offset(field) GUINT_TO_POINTER(offsetof(NetplanAddressOptions, field))
#define auth_offset(field) GUINT_TO_POINTER(offsetof(NetplanAuthenticationSettings, field))
#define ip_rule_offset(field) GUINT_TO_POINTER(offsetof(NetplanIPRule, field))
#define netdef_offset(field) GUINT_TO_POINTER(offsetof(NetplanNetDefinition, field))
#define ovs_settings_offset(field) GUINT_TO_POINTER(offsetof(NetplanOVSSettings, field))
#define route_offset(field) GUINT_TO_POINTER(offsetof(NetplanIPRoute, field))
#define wireguard_peer_offset(field) GUINT_TO_POINTER(offsetof(NetplanWireguardPeer, field))
#define vxlan_offset(field) GUINT_TO_POINTER(offsetof(NetplanVxlan, field))
/* convenience macro to avoid strdup'ing a string into a field if it's already set. */
#define set_str_if_null(dst, src) { if (dst) {\
g_assert_cmpstr(src, ==, dst); \
} else { \
dst = g_strdup(src); \
} }
extern NetplanState global_state;
NetplanParser global_parser = {0};
static gboolean
insert_kv_into_hash(void *key, void *value, void *hash);
/**
* Load YAML file into a yaml_document_t.
*
* @input_fd: the file descriptor pointing to the YAML source file
* @doc: the output document structure
*
* Returns: TRUE on success, FALSE if the document is malformed; @error gets set then.
*/
static gboolean
load_yaml_from_fd(int input_fd, yaml_document_t* doc, GError** error)
{
int in_dup = -1;
FILE* fyaml = NULL;
yaml_parser_t parser;
gboolean ret = TRUE;
in_dup = dup(input_fd);
if (in_dup < 0)
goto file_error; // LCOV_EXCL_LINE
fyaml = fdopen(in_dup, "r");
if (!fyaml)
goto file_error; // LCOV_EXCL_LINE
yaml_parser_initialize(&parser);
yaml_parser_set_input_file(&parser, fyaml);
if (!yaml_parser_load(&parser, doc)) {
ret = parser_error(&parser, NULL, error);
}
yaml_parser_delete(&parser);
fclose(fyaml);
return ret;
// LCOV_EXCL_START
file_error:
g_set_error(error, NETPLAN_FILE_ERROR, errno, "Error when opening FD %d: %m", input_fd);
if (in_dup >= 0)
close(in_dup);
return FALSE;
// LCOV_EXCL_STOP
}
/**
* Load YAML file name into a yaml_document_t.
*
* @yaml: file path to the YAML source file
* @doc: the output document structure
*
* Returns: TRUE on success, FALSE if the document is malformed; @error gets set then.
*/
static gboolean
load_yaml(const char* yaml, yaml_document_t* doc, GError** error)
{
FILE* fyaml = NULL;
yaml_parser_t parser;
gboolean ret = TRUE;
fyaml = g_fopen(yaml, "r");
if (!fyaml) { // LCOV_EXCL_START
g_set_error(error, NETPLAN_FILE_ERROR, errno, "Cannot open %s: %m", yaml);
return FALSE;
} // LCOV_EXCL_STOP
yaml_parser_initialize(&parser);
yaml_parser_set_input_file(&parser, fyaml);
if (!yaml_parser_load(&parser, doc)) {
ret = parser_error(&parser, yaml, error);
}
yaml_parser_delete(&parser);
fclose(fyaml);
return ret;
}
#define YAML_VARIABLE_NODE YAML_NO_NODE
/**
* Raise a GError about a type mismatch and return FALSE.
*/
static gboolean
assert_type_fn(const NetplanParser* npp, yaml_node_t* node, yaml_node_type_t expected_type, GError** error)
{
if (node->type == expected_type)
return TRUE;
switch (expected_type) {
case YAML_VARIABLE_NODE:
/* Special case, defer coherence checking to the next handlers */
return TRUE;
break;
case YAML_SCALAR_NODE:
yaml_error(npp, node, error, "expected scalar");
break;
case YAML_SEQUENCE_NODE:
yaml_error(npp, node, error, "expected sequence");
break;
case YAML_MAPPING_NODE:
yaml_error(npp, node, error, "expected mapping (check indentation)");
break;
// LCOV_EXCL_START
default:
g_assert_not_reached();
// LCOV_EXCL_STOP
}
return FALSE;
}
#define assert_type(ctx,n,t) { if (!assert_type_fn(ctx,n,t,error)) return FALSE; }
static inline const char*
scalar(const yaml_node_t* node)
{
return (const char*) node->data.scalar.value;
}
static void
add_missing_node(NetplanParser *npp, const yaml_node_t* node)
{
NetplanMissingNode* missing;
/* Let's capture the current netdef we were playing with along with the
* actual yaml_node_t that errors (that is an identifier not previously
* seen by the compiler). We can use it later to write an sensible error
* message and point the user in the right direction. */
missing = g_new0(NetplanMissingNode, 1);
missing->netdef_id = npp->current.netdef->id;
missing->node = node;
g_debug("recording missing yaml_node_t %s", scalar(node));
g_hash_table_insert(npp->missing_id, (gpointer)scalar(node), missing);
}
/**
* Check that node contains a valid ID/interface name. Raise GError if not.
*/
static gboolean
assert_valid_id(const NetplanParser* npp, yaml_node_t* node, GError** error)
{
static regex_t re;
static gboolean re_inited = FALSE;
assert_type(npp, node, YAML_SCALAR_NODE);
if (!re_inited) {
g_assert(regcomp(&re, "^[[:alnum:][:punct:]]+$", REG_EXTENDED|REG_NOSUB) == 0);
re_inited = TRUE;
}
if (regexec(&re, scalar(node), 0, NULL, 0) != 0)
return yaml_error(npp, node, error, "Invalid name '%s'", scalar(node));
return TRUE;
}
NetplanNetDefinition*
netplan_netdef_new(NetplanParser *npp, const char* id, NetplanDefType type, NetplanBackend backend)
{
/* create new network definition */
NetplanNetDefinition *netdef = g_new0(NetplanNetDefinition, 1);
reset_netdef(netdef, type, backend);
netdef->id = g_strdup(id);
if (!npp->parsed_defs)
npp->parsed_defs = g_hash_table_new(g_str_hash, g_str_equal);
g_hash_table_insert(npp->parsed_defs, netdef->id, netdef);
npp->ordered = g_list_append(npp->ordered, netdef);
return netdef;
}
/****************************************************
* Data types and functions for interpreting YAML nodes
****************************************************/
typedef gboolean (*node_handler) (NetplanParser* npp, yaml_node_t* node, const void* data, GError** error);
typedef gboolean (*custom_map_handler) (NetplanParser* npp, yaml_node_t* node, const char *prefix, const void* data, GError** error);
typedef struct mapping_entry_handler_s {
/* mapping key (must be scalar) */
const char* key;
/* expected type of the mapped value */
yaml_node_type_t type;
union {
node_handler generic;
custom_map_handler variable;
struct {
const struct mapping_entry_handler_s* handlers;
custom_map_handler custom;
} map;
};
/* user_data */
const void* data;
} mapping_entry_handler;
/**
* Return the #mapping_entry_handler that matches @key, or NULL if not found.
*/
static const mapping_entry_handler*
get_handler(const mapping_entry_handler* handlers, const char* key)
{
for (unsigned i = 0; handlers[i].key != NULL; ++i) {
if (g_strcmp0(handlers[i].key, key) == 0)
return &handlers[i];
}
return NULL;
}
/**
* Call handlers for all entries in a YAML mapping.
* @doc: The yaml_document_t
* @node: The yaml_node_t to process, must be a #YAML_MAPPING_NODE
* @handlers: Array of mapping_entry_handler with allowed keys
* @error: Gets set on data type errors or unknown keys
*
* Returns: TRUE on success, FALSE on error (@error gets set then).
*/
static gboolean
process_mapping(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const mapping_entry_handler* handlers, GList** out_values, GError** error)
{
yaml_node_pair_t* entry;
assert_type(npp, node, YAML_MAPPING_NODE);
for (entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) {
yaml_node_t* key, *value;
const mapping_entry_handler* h;
gboolean res = TRUE;
g_autofree char* full_key = NULL;
g_assert(error == NULL || *error == NULL);
key = yaml_document_get_node(&npp->doc, entry->key);
value = yaml_document_get_node(&npp->doc, entry->value);
assert_type(npp, key, YAML_SCALAR_NODE);
if (npp->null_fields && key_prefix) {
full_key = g_strdup_printf("%s\t%s", key_prefix, scalar(key));
if (g_hash_table_contains(npp->null_fields, full_key))
continue;
}
h = get_handler(handlers, scalar(key));
if (!h)
return yaml_error(npp, key, error, "unknown key '%s'", scalar(key));
assert_type(npp, value, h->type);
if (out_values)
*out_values = g_list_prepend(*out_values, g_strdup(scalar(key)));
if (h->type == YAML_MAPPING_NODE) {
if (h->map.custom)
res = h->map.custom(npp, value, full_key, h->data, error);
else
res = process_mapping(npp, value, full_key, h->map.handlers, NULL, error);
} else if (h->type == YAML_NO_NODE) {
res = h->variable(npp, value, full_key, h->data, error);
} else {
res = h->generic(npp, value, h->data, error);
}
if (!res)
return FALSE;
}
return TRUE;
}
/*************************************************************
* Generic helper functions to extract data from scalar nodes.
*************************************************************/
/**
* Handler for setting a guint field from a scalar node, inside a given struct
* @entryptr: pointer to the begining of the to-be-modified data structure
* @data: offset into entryptr struct where the guint field to write is located
*/
static gboolean
handle_generic_guint(NetplanParser* npp, yaml_node_t* node, const void* entryptr, const void* data, GError** error)
{
g_assert(entryptr);
guint offset = GPOINTER_TO_UINT(data);
guint64 v;
gchar* endptr;
v = g_ascii_strtoull(scalar(node), &endptr, 10);
if (*endptr != '\0' || v > G_MAXUINT)
return yaml_error(npp, node, error, "invalid unsigned int value '%s'", scalar(node));
mark_data_as_dirty(npp, entryptr + offset);
*((guint*) ((void*) entryptr + offset)) = (guint) v;
return TRUE;
}
/**
* Handler for setting a string field from a scalar node, inside a given struct
* @entryptr: pointer to the beginning of the to-be-modified data structure
* @data: offset into entryptr struct where the const char* field to write is
* located
*/
static gboolean
handle_generic_str(NetplanParser* npp, yaml_node_t* node, void* entryptr, const void* data, __unused GError** error)
{
g_assert(entryptr);
guint offset = GPOINTER_TO_UINT(data);
char** dest = (char**) ((void*) entryptr + offset);
g_free(*dest);
*dest = g_strdup(scalar(node));
mark_data_as_dirty(npp, dest);
return TRUE;
}
/*
* Handler for setting a MAC address field from a scalar node, inside a given struct
* @entryptr: pointer to the beginning of the to-be-modified data structure
* @data: offset into entryptr struct where the const char* field to write is
* located
*/
static gboolean
handle_generic_mac(NetplanParser* npp, yaml_node_t* node, void* entryptr, const void* data, GError** error)
{
g_assert(entryptr);
static regex_t re;
static gboolean re_inited = FALSE;
g_assert(node->type == YAML_SCALAR_NODE);
if (!re_inited) {
g_assert(regcomp(&re, "^[[:xdigit:]][[:xdigit:]](:[[:xdigit:]][[:xdigit:]]){5}((:[[:xdigit:]][[:xdigit:]]){14})?$", REG_EXTENDED|REG_NOSUB) == 0);
re_inited = TRUE;
}
if (regexec(&re, scalar(node), 0, NULL, 0) != 0)
return yaml_error(npp, node, error, "Invalid MAC address '%s', must be XX:XX:XX:XX:XX:XX or XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX", scalar(node));
return handle_generic_str(npp, node, entryptr, data, error);
}
/*
* Handler for setting a boolean field from a scalar node, inside a given struct
* @entryptr: pointer to the beginning of the to-be-modified data structure
* @data: offset into entryptr struct where the boolean field to write is located
*/
static gboolean
handle_generic_bool(NetplanParser* npp, yaml_node_t* node, void* entryptr, const void* data, GError** error)
{
g_assert(entryptr);
guint offset = GPOINTER_TO_UINT(data);
gboolean v;
gboolean* dest = ((void*) entryptr + offset);
if (g_ascii_strcasecmp(scalar(node), "true") == 0 ||
g_ascii_strcasecmp(scalar(node), "on") == 0 ||
g_ascii_strcasecmp(scalar(node), "yes") == 0 ||
g_ascii_strcasecmp(scalar(node), "y") == 0)
v = TRUE;
else if (g_ascii_strcasecmp(scalar(node), "false") == 0 ||
g_ascii_strcasecmp(scalar(node), "off") == 0 ||
g_ascii_strcasecmp(scalar(node), "no") == 0 ||
g_ascii_strcasecmp(scalar(node), "n") == 0)
v = FALSE;
else
return yaml_error(npp, node, error, "invalid boolean value '%s'", scalar(node));
*dest = v;
mark_data_as_dirty(npp, dest);
return TRUE;
}
/*
* Handler for setting a HashTable field from a mapping node, inside a given struct
* @entryptr: pointer to the beginning of the to-be-modified data structure
* @data: offset into entryptr struct where the boolean field to write is located
*/
static gboolean
handle_generic_tristate(NetplanParser* npp, yaml_node_t* node, void* entryptr, const void* data, GError** error)
{
g_assert(entryptr);
NetplanTristate v;
guint offset = GPOINTER_TO_UINT(data);
NetplanTristate* dest = ((void*) entryptr + offset);
if (g_ascii_strcasecmp(scalar(node), "true") == 0 ||
g_ascii_strcasecmp(scalar(node), "on") == 0 ||
g_ascii_strcasecmp(scalar(node), "yes") == 0 ||
g_ascii_strcasecmp(scalar(node), "y") == 0)
v = NETPLAN_TRISTATE_TRUE;
else if (g_ascii_strcasecmp(scalar(node), "false") == 0 ||
g_ascii_strcasecmp(scalar(node), "off") == 0 ||
g_ascii_strcasecmp(scalar(node), "no") == 0 ||
g_ascii_strcasecmp(scalar(node), "n") == 0)
v = NETPLAN_TRISTATE_FALSE;
else
return yaml_error(npp, node, error, "invalid boolean value '%s'", scalar(node));
*dest = v;
mark_data_as_dirty(npp, dest);
return TRUE;
}
/*
* Handler for setting a HashTable field from a mapping node, inside a given struct
* @entryptr: pointer to the beginning of the to-be-modified data structure
* @data: offset into entryptr struct where the boolean field to write is located
*/
static gboolean
handle_generic_map(NetplanParser *npp, yaml_node_t* node, const char* key_prefix, void* entryptr, const void* data, GError** error)
{
guint offset = GPOINTER_TO_UINT(data);
GHashTable** map = (GHashTable**) ((void*) entryptr + offset);
if (!*map)
*map = g_hash_table_new(g_str_hash, g_str_equal);
for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) {
yaml_node_t* key, *value;
key = yaml_document_get_node(&npp->doc, entry->key);
value = yaml_document_get_node(&npp->doc, entry->value);
assert_type(npp, key, YAML_SCALAR_NODE);
assert_type(npp, value, YAML_SCALAR_NODE);
if (key_prefix && npp->null_fields) {
g_autofree char* full_key = NULL;
full_key = g_strdup_printf("%s\t%s", key_prefix, key->data.scalar.value);
if (g_hash_table_contains(npp->null_fields, full_key))
continue;
}
char* stored_value = NULL;
if (g_hash_table_lookup_extended(*map, scalar(key), NULL, (void**)&stored_value)) {
/* We can safely skip this if it is the exact key/value match
* (probably caused by multi-pass processing) */
if (g_strcmp0(stored_value, scalar(value)) == 0)
continue;
return yaml_error(npp, node, error, "duplicate map entry '%s'", scalar(key));
} else
g_hash_table_insert(*map, g_strdup(scalar(key)), g_strdup(scalar(value)));
}
mark_data_as_dirty(npp, map);
return TRUE;
}
/*
* Handler for setting a DataList field from a mapping node, inside a given struct
* @entryptr: pointer to the beginning of the to-be-modified data structure
* @data: offset into entryptr struct where the boolean field to write is located
*/
static gboolean
handle_generic_datalist(NetplanParser *npp, yaml_node_t* node, const char* key_prefix, void* entryptr, const void* data, GError** error)
{
guint offset = GPOINTER_TO_UINT(data);
GData** list = (GData**) ((void*) entryptr + offset);
if (!*list)
g_datalist_init(list);
for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) {
yaml_node_t* key, *value;
g_autofree char* full_key = NULL;
key = yaml_document_get_node(&npp->doc, entry->key);
value = yaml_document_get_node(&npp->doc, entry->value);
assert_type(npp, key, YAML_SCALAR_NODE);
assert_type(npp, value, YAML_SCALAR_NODE);
if (npp->null_fields && key_prefix) {
full_key = g_strdup_printf("%s\t%s", key_prefix, scalar(key));
if (g_hash_table_contains(npp->null_fields, full_key))
continue;
}
g_datalist_id_set_data_full(list, g_quark_from_string(scalar(key)),
g_strdup(scalar(value)), g_free);
}
mark_data_as_dirty(npp, list);
return TRUE;
}
/**
* Generic handler for setting a npp->current.netdef string field from a scalar node
* @data: offset into NetplanNetDefinition where the const char* field to write is
* located
*/
static gboolean
handle_netdef_str(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
return handle_generic_str(npp, node, npp->current.netdef, data, error);
}
/**
* Generic handler for setting a npp->current.netdef ID/iface name field from a scalar node
* @data: offset into NetplanNetDefinition where the const char* field to write is
* located
*/
static gboolean
handle_netdef_id(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
if (!assert_valid_id(npp, node, error))
return FALSE;
return handle_netdef_str(npp, node, data, error);
}
static gboolean
handle_embedded_switch_mode(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
if (g_strcmp0(scalar(node), "switchdev") != 0 && g_strcmp0(scalar(node), "legacy") != 0)
return yaml_error(npp, node, error, "Value of 'embedded-switch-mode' needs to be 'switchdev' or 'legacy'");
return handle_netdef_str(npp, node, data, error);
}
static gboolean
handle_ib_mode(NetplanParser* npp, yaml_node_t* node, __unused const void* data, GError** error)
{
if (g_strcmp0(scalar(node), "datagram") == 0)
npp->current.netdef->ib_mode = NETPLAN_IB_MODE_DATAGRAM;
else if (g_strcmp0(scalar(node), "connected") == 0)
npp->current.netdef->ib_mode = NETPLAN_IB_MODE_CONNECTED;
else
return yaml_error(npp, node, error, "Value of 'infiniband-mode' needs to be 'datagram' or 'connected'");
return TRUE;
}
/**
* Generic handler for setting a npp->current.netdef ID/iface name field referring to an
* existing ID from a scalar node. This handler also includes a special case
* handler for OVS VLANs, switching the backend implicitly to OVS for such
* interfaces
* @data: offset into NetplanNetDefinition where the NetplanNetDefinition* field to write is
* located
*/
static gboolean
handle_netdef_id_ref(NetplanParser* npp, yaml_node_t* node, const void* data, __unused GError** error)
{
guint offset = GPOINTER_TO_UINT(data);
NetplanNetDefinition* ref = NULL;
NetplanNetDefinition** dest = (void*) npp->current.netdef + offset;
ref = g_hash_table_lookup(npp->parsed_defs, scalar(node));
if (!ref) {
add_missing_node(npp, node);
} else {
NetplanNetDefinition* netdef = npp->current.netdef;
*dest = ref;
if (netdef->type == NETPLAN_DEF_TYPE_VLAN && ref->backend == NETPLAN_BACKEND_OVS) {
g_debug("%s: VLAN defined for Open vSwitch interface, choosing OVS backend", netdef->id);
netdef->backend = NETPLAN_BACKEND_OVS;
}
}
mark_data_as_dirty(npp, dest);
return TRUE;
}
/**
* Handler for setting a npp->current.netdef ID/iface name field referring to an
* existing ID from a scalar node.
* @data: offset into NetplanVxlan where the NetplanNetDefinition* field to
* write is located
*/
static gboolean
handle_vxlan_id_ref(NetplanParser* npp, yaml_node_t* node, const void* data, __unused GError** error)
{
guint offset = GPOINTER_TO_UINT(data);
NetplanNetDefinition* ref = NULL;
NetplanNetDefinition** dest = (void*) npp->current.vxlan + offset;
ref = g_hash_table_lookup(npp->parsed_defs, scalar(node));
if (!ref)
add_missing_node(npp, node);
else
*dest = ref;
mark_data_as_dirty(npp, dest);
return TRUE;
}
/**
* Generic handler for setting a npp->current.netdef MAC address field from a scalar node
* @data: offset into NetplanNetDefinition where the const char* field to write is
* located
*/
static gboolean
handle_netdef_mac(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
return handle_generic_mac(npp, node, npp->current.netdef, data, error);
}
/**
* Generic handler for setting a npp->current.netdef gboolean field from a scalar node
* @data: offset into NetplanNetDefinition where the gboolean field to write is located
*/
static gboolean
handle_netdef_bool(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
return handle_generic_bool(npp, node, npp->current.netdef, data, error);
}
/**
* Generic handler for tri-state settings that can bei "UNSET", "TRUE", or "FALSE".
* @data: offset into NetplanNetDefinition where the guint field to write is located
*/
static gboolean
handle_netdef_tristate(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
return handle_generic_tristate(npp, node, npp->current.netdef, data, error);
}
/**
* Generic handler for setting a npp->current.netdef guint field from a scalar node
* @data: offset into NetplanNetDefinition where the guint field to write is located
*/
static gboolean
handle_netdef_guint(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
return handle_generic_guint(npp, node, npp->current.netdef, data, error);
}
static gboolean
handle_netdef_ip4(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
guint offset = GPOINTER_TO_UINT(data);
char** dest = (char**) ((void*) npp->current.netdef + offset);
g_autofree char* addr = NULL;
char* prefix_len;
/* these addresses can't have /prefix_len */
addr = g_strdup(scalar(node));
prefix_len = strrchr(addr, '/');
/* FIXME: stop excluding this from coverage; refactor address handling instead */
// LCOV_EXCL_START
if (prefix_len)
return yaml_error(npp, node, error,
"invalid address: a single IPv4 address (without /prefixlength) is required");
/* is it an IPv4 address? */
if (!is_ip4_address(addr))
return yaml_error(npp, node, error,
"invalid IPv4 address: %s", scalar(node));
// LCOV_EXCL_STOP
g_free(*dest);
*dest = g_strdup(scalar(node));
mark_data_as_dirty(npp, dest);
return TRUE;
}
static gboolean
handle_netdef_ip6(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
guint offset = GPOINTER_TO_UINT(data);
char** dest = (char**) ((void*) npp->current.netdef + offset);
g_autofree char* addr = NULL;
char* prefix_len;
/* these addresses can't have /prefix_len */
addr = g_strdup(scalar(node));
prefix_len = strrchr(addr, '/');
/* FIXME: stop excluding this from coverage; refactor address handling instead */
// LCOV_EXCL_START
if (prefix_len)
return yaml_error(npp, node, error,
"invalid address: a single IPv6 address (without /prefixlength) is required");
/* is it an IPv6 address? */
if (!is_ip6_address(addr))
return yaml_error(npp, node, error,
"invalid IPv6 address: %s", scalar(node));
// LCOV_EXCL_STOP
g_free(*dest);
*dest = g_strdup(scalar(node));
mark_data_as_dirty(npp, dest);
return TRUE;
}
static gboolean
handle_netdef_addrgen(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
g_assert(npp->current.netdef);
if (strcmp(scalar(node), "eui64") == 0)
npp->current.netdef->ip6_addr_gen_mode = NETPLAN_ADDRGEN_EUI64;
else if (strcmp(scalar(node), "stable-privacy") == 0)
npp->current.netdef->ip6_addr_gen_mode = NETPLAN_ADDRGEN_STABLEPRIVACY;
else
return yaml_error(npp, node, error, "unknown ipv6-address-generation '%s'", scalar(node));
return TRUE;
}
static gboolean
handle_netdef_addrtok(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
g_assert(npp->current.netdef);
gboolean ret = handle_netdef_str(npp, node, data, error);
if (!is_ip6_address(npp->current.netdef->ip6_addr_gen_token))
return yaml_error(npp, node, error, "invalid ipv6-address-token '%s'", scalar(node));
return ret;
}
static gboolean
handle_netdef_map(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const void* data, GError** error)
{
g_assert(npp->current.netdef);
return handle_generic_map(npp, node, key_prefix, npp->current.netdef, data, error);
}
static gboolean
handle_netdef_backend_settings_str(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
npp->current.netdef->has_backend_settings_nm = TRUE;
return handle_generic_str(npp, node, npp->current.netdef, data, error);
}
/*
* Check if the passthrough key format is incorrect and remove it from the list.
* user_data is expected to contain a pointer to the GData list.
*/
static void
validate_kf_group_key(GQuark key_id, __unused gpointer value, gpointer user_data)
{
GData** list = user_data;
const gchar* key = g_quark_to_string(key_id);
gchar** group_key = g_strsplit(key, ".", -1);
if (g_strv_length(group_key) < 2) {
g_warning("NetworkManager: passthrough key '%s' format is invalid, should be 'group.key'.", key);
g_datalist_id_remove_data(list, key_id);
}
g_strfreev(group_key);
}
static gboolean
handle_netdef_passthrough_datalist(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const void* data, GError** error)
{
g_assert(npp->current.netdef);
gboolean ret = handle_generic_datalist(npp, node, key_prefix, npp->current.netdef, data, error);
GData** list = &npp->current.netdef->backend_settings.passthrough;
g_datalist_foreach(list, validate_kf_group_key, list);
if (*list == NULL) {
g_datalist_clear(list);
}
npp->current.netdef->has_backend_settings_nm = TRUE;
return ret;
}
static gboolean
handle_veth_peer(NetplanParser* npp, yaml_node_t* node, __unused const void* data, GError** error)
{
NetplanNetDefinition* netdef = npp->current.netdef;
if (!g_strcmp0(netdef->id, scalar(node)))
return yaml_error(npp, node, error, "%s: virtual-ethernet peer cannot be itself", netdef->id);
NetplanNetDefinition* link = g_hash_table_lookup(npp->parsed_defs, scalar(node));
if (link) {
if (link->type != NETPLAN_DEF_TYPE_VETH && link->type != NETPLAN_DEF_TYPE_NM_PLACEHOLDER_)
return yaml_error(npp, node, error, "%s: virtual-ethernet peer '%s' is not a virtual-ethernet interface", netdef->id, link->id);
if (link->veth_peer_link && link->veth_peer_link != netdef)
return yaml_error(npp, node, error, "%s: virtual-ethernet peer '%s' is another virtual-ethernet's (%s) peer already",
netdef->id, link->id, link->veth_peer_link->id);
netdef->veth_peer_link = link;
link->veth_peer_link = netdef;
return TRUE;
}
add_missing_node(npp, node);
return TRUE;
}
/****************************************************
* Grammar and handlers for network config "match" entry
****************************************************/
static gboolean
handle_match_driver(NetplanParser* npp, yaml_node_t* node, __unused const char* key_prefix, __unused const void* _, GError** error)
{
gboolean ret = FALSE;
yaml_node_t *elem = NULL;
g_autoptr(GString) sequence = NULL;
/* We overload the 'driver' setting for matches; such that it can either be a
* single scalar specifying a single driver glob/match, or a sequence of many
* globs any of which must match. */
if (node->type == YAML_SCALAR_NODE) {
if (g_strrstr(scalar(node), " "))
return yaml_error(npp, node, error, "A 'driver' glob cannot contain whitespace");
ret = handle_netdef_str(npp, node, netdef_offset(match.driver), error);
} else if (node->type == YAML_SEQUENCE_NODE) {
for (yaml_node_item_t *iter = node->data.sequence.items.start; iter < node->data.sequence.items.top; iter++) {
elem = yaml_document_get_node(&npp->doc, *iter);
assert_type(npp, elem, YAML_SCALAR_NODE);
if (g_strrstr(scalar(elem), " "))
return yaml_error(npp, node, error, "A 'driver' glob cannot contain whitespace");
if (!sequence)
sequence = g_string_new(scalar(elem));
else
g_string_append_printf(sequence, "\t%s", scalar(elem)); /* tab separated */
}
if (!sequence)
return yaml_error(npp, node, error, "invalid sequence for 'driver'");
npp->current.netdef->match.driver = g_strdup(sequence->str);
ret = TRUE;
} else
return yaml_error(npp, node, error, "invalid type for 'driver': must be a scalar or a sequence of scalars");
return ret;
}
static const mapping_entry_handler match_handlers[] = {
{"driver", YAML_NO_NODE, {.variable=handle_match_driver}, NULL},
{"macaddress", YAML_SCALAR_NODE, {.generic=handle_netdef_mac}, netdef_offset(match.mac)},
{"name", YAML_SCALAR_NODE, {.generic=handle_netdef_id}, netdef_offset(match.original_name)},
{NULL}
};
/****************************************************
* Grammar and handlers for network config "auth" entry
****************************************************/
static gboolean
handle_auth_str(NetplanParser* npp, yaml_node_t* node, const void* data, __unused GError** error)
{
g_assert(npp->current.auth);
guint offset = GPOINTER_TO_UINT(data);
char** dest = (char**) ((void*) npp->current.auth + offset);
g_free(*dest);
*dest = g_strdup(scalar(node));
mark_data_as_dirty(npp, dest);
return TRUE;
}
static gboolean
handle_auth_key_management(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
NetplanAuthenticationSettings* auth = npp->current.auth;
g_assert(auth);
if (strcmp(scalar(node), "none") == 0)
auth->key_management = NETPLAN_AUTH_KEY_MANAGEMENT_NONE;
else if (strcmp(scalar(node), "psk") == 0)
auth->key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK;
else if (strcmp(scalar(node), "eap") == 0)
auth->key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP;
else if (strcmp(scalar(node), "eap-sha256") == 0) {
/* WPA-EAP-SHA256 is commonly used with Protected Management Frames
* so let's set it as optional
*/
auth->key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSHA256;
auth->pmf_mode = NETPLAN_AUTH_PMF_MODE_OPTIONAL;
}
else if (strcmp(scalar(node), "eap-suite-b-192") == 0) {
/* Settings for WPA3-Enterprise for sensitive enterprise environments.
* Protected Management Frames (ieee80211w) is mandatory.
*/
auth->key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAPSUITE_B_192;
auth->pmf_mode = NETPLAN_AUTH_PMF_MODE_REQUIRED;
}
else if (strcmp(scalar(node), "sae") == 0) {
/* SAE is used by WPA3 and Protected Management Frames
* (ieee80211w) is mandatory.
*/
auth->key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_SAE;
auth->pmf_mode = NETPLAN_AUTH_PMF_MODE_REQUIRED;
}
else if (strcmp(scalar(node), "802.1x") == 0)
auth->key_management = NETPLAN_AUTH_KEY_MANAGEMENT_8021X;
else
return yaml_error(npp, node, error, "unknown key management type '%s'", scalar(node));
return TRUE;
}
static gboolean
handle_auth_method(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
NetplanAuthenticationSettings* auth = npp->current.auth;
g_assert(auth);
if (strcmp(scalar(node), "tls") == 0)
auth->eap_method = NETPLAN_AUTH_EAP_TLS;
else if (strcmp(scalar(node), "peap") == 0)
auth->eap_method = NETPLAN_AUTH_EAP_PEAP;
else if (strcmp(scalar(node), "ttls") == 0)
auth->eap_method = NETPLAN_AUTH_EAP_TTLS;
else if (strcmp(scalar(node), "leap") == 0)
auth->eap_method = NETPLAN_AUTH_EAP_LEAP;
else if (strcmp(scalar(node), "pwd") == 0)
auth->eap_method = NETPLAN_AUTH_EAP_PWD;
else
return yaml_error(npp, node, error, "unknown EAP method '%s'", scalar(node));
return TRUE;
}
static const mapping_entry_handler auth_handlers[] = {
{"key-management", YAML_SCALAR_NODE, {.generic=handle_auth_key_management}, NULL},
{"method", YAML_SCALAR_NODE, {.generic=handle_auth_method}, NULL},
{"identity", YAML_SCALAR_NODE, {.generic=handle_auth_str}, auth_offset(identity)},
{"anonymous-identity", YAML_SCALAR_NODE, {.generic=handle_auth_str}, auth_offset(anonymous_identity)},
{"password", YAML_SCALAR_NODE, {.generic=handle_auth_str}, auth_offset(password)},
{"ca-certificate", YAML_SCALAR_NODE, {.generic=handle_auth_str}, auth_offset(ca_certificate)},
{"client-certificate", YAML_SCALAR_NODE, {.generic=handle_auth_str}, auth_offset(client_certificate)},
{"client-key", YAML_SCALAR_NODE, {.generic=handle_auth_str}, auth_offset(client_key)},
{"client-key-password", YAML_SCALAR_NODE, {.generic=handle_auth_str}, auth_offset(client_key_password)},
{"phase2-auth", YAML_SCALAR_NODE, {.generic=handle_auth_str}, auth_offset(phase2_auth)},
{NULL}
};
/****************************************************
* Grammar and handlers for network device definition
****************************************************/
NetplanBackend
get_default_backend_for_type(NetplanBackend global_backend, __unused NetplanDefType type)
{
if (global_backend != NETPLAN_BACKEND_NONE)
return global_backend;
/* networkd can handle all device types at the moment, so nothing
* type-specific */
return NETPLAN_BACKEND_NETWORKD;
}
static gboolean
handle_ap_backend_settings_str(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
npp->current.netdef->has_backend_settings_nm = TRUE;
return handle_generic_str(npp, node, npp->current.access_point, data, error);
}
static gboolean
handle_access_point_datalist(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const void* data, GError** error)
{
g_assert(npp->current.access_point);
gboolean ret = handle_generic_datalist(npp, node, key_prefix, npp->current.access_point, data, error);
GData** list = &npp->current.access_point->backend_settings.passthrough;
g_datalist_foreach(list, validate_kf_group_key, list);
if (*list == NULL) {
g_datalist_clear(list);
}
npp->current.netdef->has_backend_settings_nm = TRUE;
return ret;
}
static gboolean
handle_access_point_guint(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
return handle_generic_guint(npp, node, npp->current.access_point, data, error);
}
static gboolean
handle_access_point_mac(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
return handle_generic_mac(npp, node, npp->current.access_point, data, error);
}
static gboolean
handle_access_point_bool(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
return handle_generic_bool(npp, node, npp->current.access_point, data, error);
}
static gboolean
handle_access_point_password(NetplanParser* npp, yaml_node_t* node, __unused const void* _, __unused GError** error)
{
NetplanWifiAccessPoint *access_point = npp->current.access_point;
g_assert(access_point);
/* shortcut for WPA-PSK */
access_point->has_auth = TRUE;
if (access_point->auth.key_management == NETPLAN_AUTH_KEY_MANAGEMENT_NONE)
access_point->auth.key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK;
access_point->auth.pmf_mode = NETPLAN_AUTH_PMF_MODE_OPTIONAL;
g_free(access_point->auth.psk);
access_point->auth.psk = g_strdup(scalar(node));
return TRUE;
}
static gboolean
handle_access_point_auth(NetplanParser* npp, yaml_node_t* node, __unused const char* key_prefix, __unused const void* _, GError** error)
{
NetplanWifiAccessPoint *access_point = npp->current.access_point;
gboolean ret;
g_assert(access_point);
access_point->has_auth = TRUE;
npp->current.auth = &access_point->auth;
ret = process_mapping(npp, node, NULL, auth_handlers, NULL, error);
npp->current.auth = NULL;
return ret;
}
static gboolean
handle_access_point_mode(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
NetplanWifiAccessPoint *access_point = npp->current.access_point;
g_assert(access_point);
if (strcmp(scalar(node), "infrastructure") == 0)
access_point->mode = NETPLAN_WIFI_MODE_INFRASTRUCTURE;
else if (strcmp(scalar(node), "adhoc") == 0)
access_point->mode = NETPLAN_WIFI_MODE_ADHOC;
else if (strcmp(scalar(node), "ap") == 0)
access_point->mode = NETPLAN_WIFI_MODE_AP;
else
return yaml_error(npp, node, error, "unknown wifi mode '%s'", scalar(node));
return TRUE;
}
static gboolean
handle_access_point_band(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
NetplanWifiAccessPoint *access_point = npp->current.access_point;
g_assert(access_point);
if (strcmp(scalar(node), "5GHz") == 0 || strcmp(scalar(node), "5G") == 0)
access_point->band = NETPLAN_WIFI_BAND_5;
else if (strcmp(scalar(node), "2.4GHz") == 0 || strcmp(scalar(node), "2.4G") == 0)
access_point->band = NETPLAN_WIFI_BAND_24;
else
return yaml_error(npp, node, error, "unknown wifi band '%s'", scalar(node));
return TRUE;
}
static gboolean
handle_tunnel_key_flags(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
gboolean found = FALSE;
assert_type(npp, entry, YAML_SCALAR_NODE);
for (int i = 1; i < NETPLAN_KEY_FLAG_MAX_; i <<= 1) {
if (!g_ascii_strcasecmp(scalar(entry), netplan_key_flags_name(i))) {
npp->current.netdef->tunnel_private_key_flags |= i;
found = TRUE;
}
}
if (!found)
return yaml_error(npp, node, error,
"Key flag '%s' is not supported. Valid values are \"agent-owned\", \"not-saved\" and \"not-required\"",
scalar(entry));
}
return TRUE;
}
/* Keep in sync with ap_nm_backend_settings_handlers */
static const mapping_entry_handler nm_backend_settings_handlers[] = {
{"name", YAML_SCALAR_NODE, {.generic=handle_netdef_backend_settings_str}, netdef_offset(backend_settings.name)},
{"uuid", YAML_SCALAR_NODE, {.generic=handle_netdef_backend_settings_str}, netdef_offset(backend_settings.uuid)},
{"stable-id", YAML_SCALAR_NODE, {.generic=handle_netdef_backend_settings_str}, netdef_offset(backend_settings.stable_id)},
{"device", YAML_SCALAR_NODE, {.generic=handle_netdef_backend_settings_str}, netdef_offset(backend_settings.device)},
/* Fallback mode, to support all NM settings of the NetworkManager netplan backend */
{"passthrough", YAML_MAPPING_NODE, {.map={.custom=handle_netdef_passthrough_datalist}}, netdef_offset(backend_settings.passthrough)},
{NULL}
};
/* Keep in sync with nm_backend_settings_handlers */
static const mapping_entry_handler ap_nm_backend_settings_handlers[] = {
{"name", YAML_SCALAR_NODE, {.generic=handle_ap_backend_settings_str}, access_point_offset(backend_settings.name)},
{"uuid", YAML_SCALAR_NODE, {.generic=handle_ap_backend_settings_str}, access_point_offset(backend_settings.uuid)},
{"stable-id", YAML_SCALAR_NODE, {.generic=handle_ap_backend_settings_str}, access_point_offset(backend_settings.stable_id)},
{"device", YAML_SCALAR_NODE, {.generic=handle_ap_backend_settings_str}, access_point_offset(backend_settings.device)},
/* Fallback mode, to support all NM settings of the NetworkManager netplan backend */
{"passthrough", YAML_MAPPING_NODE, {.map={.custom=handle_access_point_datalist}}, access_point_offset(backend_settings.passthrough)},
{NULL}
};
static const mapping_entry_handler wifi_access_point_handlers[] = {
{"band", YAML_SCALAR_NODE, {.generic=handle_access_point_band}, NULL},
{"bssid", YAML_SCALAR_NODE, {.generic=handle_access_point_mac}, access_point_offset(bssid)},
{"hidden", YAML_SCALAR_NODE, {.generic=handle_access_point_bool}, access_point_offset(hidden)},
{"channel", YAML_SCALAR_NODE, {.generic=handle_access_point_guint}, access_point_offset(channel)},
{"mode", YAML_SCALAR_NODE, {.generic=handle_access_point_mode}, NULL},
{"password", YAML_SCALAR_NODE, {.generic=handle_access_point_password}, NULL},
{"auth", YAML_MAPPING_NODE, {.map={.custom=handle_access_point_auth}}, NULL},
{"networkmanager", YAML_MAPPING_NODE, {.map={.handlers=ap_nm_backend_settings_handlers}}, NULL},
{NULL}
};
/**
* Parse scalar node's string into a netdef_backend.
*/
static gboolean
parse_renderer(NetplanParser* npp, yaml_node_t* node, NetplanBackend* backend, GError** error)
{
if (strcmp(scalar(node), "networkd") == 0)
*backend = NETPLAN_BACKEND_NETWORKD;
else if (strcmp(scalar(node), "NetworkManager") == 0)
*backend = NETPLAN_BACKEND_NM;
else
return yaml_error(npp, node, error, "unknown renderer '%s'", scalar(node));
mark_data_as_dirty(npp, backend);
return TRUE;
}
static gboolean
handle_netdef_renderer(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
if (npp->current.netdef->type == NETPLAN_DEF_TYPE_VLAN) {
if (strcmp(scalar(node), "sriov") == 0) {
npp->current.netdef->sriov_vlan_filter = TRUE;
return TRUE;
}
}
return parse_renderer(npp, node, &npp->current.netdef->backend, error);
}
static gboolean
handle_accept_ra(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
gboolean ret = handle_generic_bool(npp, node, npp->current.netdef, data, error);
if (npp->current.netdef->accept_ra)
npp->current.netdef->accept_ra = NETPLAN_RA_MODE_ENABLED;
else
npp->current.netdef->accept_ra = NETPLAN_RA_MODE_DISABLED;
return ret;
}
static gboolean
handle_activation_mode(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
if (g_strcmp0(scalar(node), "manual") && g_strcmp0(scalar(node), "off"))
return yaml_error(npp, node, error, "Value of 'activation-mode' needs to be 'manual' or 'off'");
return handle_netdef_str(npp, node, data, error);
}
static gboolean
handle_match(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, __unused const void* _, GError** error)
{
npp->current.netdef->has_match = TRUE;
return process_mapping(npp, node, key_prefix, match_handlers, NULL, error);
}
NETPLAN_ABI struct NetplanWifiWowlanType
NETPLAN_WIFI_WOWLAN_TYPES[] = {
{"default", NETPLAN_WIFI_WOWLAN_DEFAULT},
{"any", NETPLAN_WIFI_WOWLAN_ANY},
{"disconnect", NETPLAN_WIFI_WOWLAN_DISCONNECT},
{"magic_pkt", NETPLAN_WIFI_WOWLAN_MAGIC},
{"gtk_rekey_failure", NETPLAN_WIFI_WOWLAN_GTK_REKEY_FAILURE},
{"eap_identity_req", NETPLAN_WIFI_WOWLAN_EAP_IDENTITY_REQ},
{"four_way_handshake", NETPLAN_WIFI_WOWLAN_4WAY_HANDSHAKE},
{"rfkill_release", NETPLAN_WIFI_WOWLAN_RFKILL_RELEASE},
{"tcp", NETPLAN_WIFI_WOWLAN_TCP},
{NULL},
};
static gboolean
handle_wowlan(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
assert_type(npp, entry, YAML_SCALAR_NODE);
int found = FALSE;
for (unsigned i = 0; NETPLAN_WIFI_WOWLAN_TYPES[i].name != NULL; ++i) {
if (g_ascii_strcasecmp(scalar(entry), NETPLAN_WIFI_WOWLAN_TYPES[i].name) == 0) {
npp->current.netdef->wowlan |= NETPLAN_WIFI_WOWLAN_TYPES[i].flag;
found = TRUE;
break;
}
}
if (!found)
return yaml_error(npp, node, error, "invalid value for wakeonwlan: '%s'", scalar(entry));
}
if (npp->current.netdef->wowlan > NETPLAN_WIFI_WOWLAN_DEFAULT && npp->current.netdef->wowlan & NETPLAN_WIFI_WOWLAN_TYPES[0].flag)
return yaml_error(npp, node, error, "'default' is an exclusive flag for wakeonwlan");
return TRUE;
}
static gboolean
handle_auth(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, __unused const void* _, GError** error)
{
gboolean ret;
npp->current.netdef->has_auth = TRUE;
npp->current.auth = &npp->current.netdef->auth;
ret = process_mapping(npp, node, key_prefix, auth_handlers, NULL, error);
mark_data_as_dirty(npp, &npp->current.netdef->auth);
npp->current.auth = NULL;
return ret;
}
static gboolean
handle_address_option_lifetime(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
if (g_ascii_strcasecmp(scalar(node), "0") != 0 &&
g_ascii_strcasecmp(scalar(node), "forever") != 0) {
return yaml_error(npp, node, error, "invalid lifetime value '%s'", scalar(node));
}
return handle_generic_str(npp, node, npp->current.addr_options, data, error);
}
static gboolean
handle_address_option_label(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
return handle_generic_str(npp, node, npp->current.addr_options, data, error);
}
const mapping_entry_handler address_option_handlers[] = {
{"lifetime", YAML_SCALAR_NODE, {.generic=handle_address_option_lifetime}, addr_option_offset(lifetime)},
{"label", YAML_SCALAR_NODE, {.generic=handle_address_option_label}, addr_option_offset(label)},
{NULL}
};
/*
* Handler for setting an array of IP addresses from a sequence node, inside a given struct
* @entryptr: pointer to the beginning of the do-be-modified data structure
* @data: offset into entryptr struct where the array to write is located
*/
static gboolean
handle_generic_addresses(NetplanParser* npp, yaml_node_t* node, gboolean check_zero_prefix, GArray** ip4, GArray** ip6, GError** error)
{
g_assert(ip4);
g_assert(ip6);
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
g_autofree char* addr = NULL;
char* prefix_len;
guint64 prefix_len_num;
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
yaml_node_t *key = NULL;
yaml_node_t *value = NULL;
if (entry->type != YAML_SCALAR_NODE && entry->type != YAML_MAPPING_NODE) {
return yaml_error(npp, entry, error, "expected either scalar or mapping (check indentation)");
}
if (entry->type == YAML_MAPPING_NODE) {
key = yaml_document_get_node(&npp->doc, entry->data.mapping.pairs.start->key);
value = yaml_document_get_node(&npp->doc, entry->data.mapping.pairs.start->value);
entry = key;
}
assert_type(npp, entry, YAML_SCALAR_NODE);
/* split off /prefix_len */
addr = g_strdup(scalar(entry));
prefix_len = strrchr(addr, '/');
if (!prefix_len)
return yaml_error(npp, node, error, "address '%s' is missing /prefixlength", scalar(entry));
*prefix_len = '\0';
prefix_len++; /* skip former '/' into first char of prefix */
prefix_len_num = g_ascii_strtoull(prefix_len, NULL, 10);
if (value) {
if (!is_ip4_address(addr) && !is_ip6_address(addr))
return yaml_error(npp, node, error, "malformed address '%s', must be X.X.X.X/NN or X:X:X:X:X:X:X:X/NN", scalar(entry));
if (!npp->current.netdef->address_options)
npp->current.netdef->address_options = g_array_new(FALSE, FALSE, sizeof(NetplanAddressOptions*));
for (unsigned i = 0; i < npp->current.netdef->address_options->len; ++i) {
NetplanAddressOptions* opts = g_array_index(npp->current.netdef->address_options, NetplanAddressOptions*, i);
/* check for multi-pass parsing, return early if options for this address already exist */
if (!g_strcmp0(scalar(key), opts->address))
return TRUE;
}
npp->current.addr_options = g_new0(NetplanAddressOptions, 1);
npp->current.addr_options->address = g_strdup(scalar(key));
if (!process_mapping(npp, value, NULL, address_option_handlers, NULL, error))
return FALSE;
g_array_append_val(npp->current.netdef->address_options, npp->current.addr_options);
mark_data_as_dirty(npp, &npp->current.netdef->address_options);
npp->current.addr_options = NULL;
continue;
}
/* is it an IPv4 address? */
if (is_ip4_address(addr)) {
if ((check_zero_prefix && prefix_len_num == 0) || prefix_len_num > 32)
return yaml_error(npp, node, error, "invalid prefix length in address '%s'", scalar(entry));
if (!*ip4)
*ip4 = g_array_new(FALSE, FALSE, sizeof(char*));
/* Do not append the same IP (on multiple passes), if it is already contained */
for (unsigned i = 0; i < (*ip4)->len; ++i)
if (!g_strcmp0(scalar(entry), g_array_index(*ip4, char*, i)))
goto skip_ip4;
char* s = g_strdup(scalar(entry));
g_array_append_val(*ip4, s);
mark_data_as_dirty(npp, ip4);
skip_ip4:
continue;
}
/* is it an IPv6 address? */
if (is_ip6_address(addr)) {
if ((check_zero_prefix && prefix_len_num == 0) || prefix_len_num > 128)
return yaml_error(npp, node, error, "invalid prefix length in address '%s'", scalar(entry));
if (!*ip6)
*ip6 = g_array_new(FALSE, FALSE, sizeof(char*));
/* Do not append the same IP (on multiple passes), if it is already contained */
for (unsigned i = 0; i < (*ip6)->len; ++i)
if (!g_strcmp0(scalar(entry), g_array_index(*ip6, char*, i)))
goto skip_ip6;
char* s = g_strdup(scalar(entry));
g_array_append_val(*ip6, s);
mark_data_as_dirty(npp, ip6);
skip_ip6:
continue;
}
return yaml_error(npp, node, error, "malformed address '%s', must be X.X.X.X/NN or X:X:X:X:X:X:X:X/NN", scalar(entry));
}
return TRUE;
}
static gboolean
handle_addresses(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
return handle_generic_addresses(npp, node, TRUE, &(npp->current.netdef->ip4_addresses), &(npp->current.netdef->ip6_addresses), error);
}
static gboolean
handle_gateway4(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
if (!is_ip4_address(scalar(node)))
return yaml_error(npp, node, error, "invalid IPv4 address '%s'", scalar(node));
set_str_if_null(npp->current.netdef->gateway4, scalar(node));
mark_data_as_dirty(npp, &npp->current.netdef->gateway4);
g_warning("`gateway4` has been deprecated, use default routes instead.\n"
"See the 'Default routes' section of the documentation for more details.");
return TRUE;
}
static gboolean
handle_gateway6(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
if (!is_ip6_address(scalar(node)))
return yaml_error(npp, node, error, "invalid IPv6 address '%s'", scalar(node));
set_str_if_null(npp->current.netdef->gateway6, scalar(node));
mark_data_as_dirty(npp, &npp->current.netdef->gateway6);
g_warning("`gateway6` has been deprecated, use default routes instead.\n"
"See the 'Default routes' section of the documentation for more details.");
return TRUE;
}
static gboolean
handle_wifi_access_points(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, __unused const void* data, GError** error)
{
GHashTable* access_points = g_hash_table_new(g_str_hash, g_str_equal);
for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) {
NetplanWifiAccessPoint *access_point = NULL;
g_autofree char* full_key = NULL;
yaml_node_t* key, *value;
const gchar* ssid;
key = yaml_document_get_node(&npp->doc, entry->key);
assert_type(npp, key, YAML_SCALAR_NODE);
value = yaml_document_get_node(&npp->doc, entry->value);
assert_type(npp, value, YAML_MAPPING_NODE);
if (key_prefix && npp->null_fields) {
full_key = g_strdup_printf("%s\t%s", key_prefix, key->data.scalar.value);
if (g_hash_table_contains(npp->null_fields, full_key))
continue;
}
ssid = scalar(key);
/*
* Delete the access-point if it already exists in the netdef and let the new
* one be added. It has the side effect of reprocessing APs if the parser requires a
* second pass.
*
* TODO: implement support for merging AP settings if they were previously defined
*/
if (npp->current.netdef->access_points && g_hash_table_contains(npp->current.netdef->access_points, ssid)) {
NetplanWifiAccessPoint *ap = g_hash_table_lookup(npp->current.netdef->access_points, ssid);
g_hash_table_remove(npp->current.netdef->access_points, ssid);
free_access_point(NULL, ap, NULL);
}
/* Check if the SSID was already defined in the same netdef in this YAML file we are parsing */
if (g_hash_table_contains(access_points, ssid)) {
g_hash_table_foreach(access_points, free_access_point, NULL);
g_hash_table_destroy(access_points);
return yaml_error(npp, key, error, "%s: Duplicate access point SSID '%s'", npp->current.netdef->id, ssid);
}
g_assert(access_point == NULL);
access_point = g_new0(NetplanWifiAccessPoint, 1);
access_point->ssid = g_strdup(ssid);
g_debug("%s: adding wifi AP '%s'", npp->current.netdef->id, access_point->ssid);
npp->current.access_point = access_point;
if (!process_mapping(npp, value, full_key, wifi_access_point_handlers, NULL, error)) {
access_point_clear(&npp->current.access_point, npp->current.backend);
g_hash_table_foreach(access_points, free_access_point, NULL);
g_hash_table_destroy(access_points);
return FALSE;
}
g_hash_table_insert(access_points, access_point->ssid, access_point);
npp->current.access_point = NULL;
}
if (g_hash_table_size(access_points) > 0) {
if (!npp->current.netdef->access_points)
npp->current.netdef->access_points = g_hash_table_new(g_str_hash, g_str_equal);
g_hash_table_foreach_steal(access_points, insert_kv_into_hash, npp->current.netdef->access_points);
mark_data_as_dirty(npp, &npp->current.netdef->access_points);
}
g_hash_table_destroy(access_points);
return TRUE;
}
/**
* Handler for bridge "interfaces:" list. We don't store that list in npp->current.netdef,
* but set npp->current.netdef's ID in all listed interfaces' "bond" or "bridge" field.
* @data: ignored
*/
static gboolean
handle_bridge_interfaces(NetplanParser* npp, yaml_node_t* node, __unused const void* data, GError** error)
{
/* all entries must refer to already defined IDs */
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
NetplanNetDefinition *component;
assert_type(npp, entry, YAML_SCALAR_NODE);
component = g_hash_table_lookup(npp->parsed_defs, scalar(entry));
if (!component) {
add_missing_node(npp, entry);
} else {
if (component->bridge && g_strcmp0(component->bridge, npp->current.netdef->id) != 0)
return yaml_error(npp, node, error, "%s: interface '%s' is already assigned to bridge %s",
npp->current.netdef->id, scalar(entry), component->bridge);
if (component->bond)
return yaml_error(npp, node, error, "%s: interface '%s' is already assigned to bond %s",
npp->current.netdef->id, scalar(entry), component->bond);
set_str_if_null(component->bridge, npp->current.netdef->id);
component->bridge_link = npp->current.netdef;
if (component->backend == NETPLAN_BACKEND_OVS) {
g_debug("%s: Bridge contains Open vSwitch interface, choosing OVS backend", npp->current.netdef->id);
npp->current.netdef->backend = NETPLAN_BACKEND_OVS;
}
}
}
return TRUE;
}
/**
* Handler for bond "mode" types.
* @data: offset into NetplanNetDefinition where the const char* field to write is
* located
*/
static gboolean
handle_bond_mode(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
if (!(strcmp(scalar(node), "balance-rr") == 0 ||
strcmp(scalar(node), "active-backup") == 0 ||
strcmp(scalar(node), "balance-xor") == 0 ||
strcmp(scalar(node), "broadcast") == 0 ||
strcmp(scalar(node), "802.3ad") == 0 ||
strcmp(scalar(node), "balance-tlb") == 0 ||
strcmp(scalar(node), "balance-alb") == 0 ||
strcmp(scalar(node), "balance-tcp") == 0 || // only supported for OVS
strcmp(scalar(node), "balance-slb") == 0)) // only supported for OVS
return yaml_error(npp, node, error, "unknown bond mode '%s'", scalar(node));
/* Implicitly set NETPLAN_BACKEND_OVS if ovs-only mode selected */
if (!strcmp(scalar(node), "balance-tcp") ||
!strcmp(scalar(node), "balance-slb")) {
g_debug("%s: mode '%s' only supported with Open vSwitch, choosing this backend",
npp->current.netdef->id, scalar(node));
npp->current.netdef->backend = NETPLAN_BACKEND_OVS;
}
return handle_netdef_str(npp, node, data, error);
}
/**
* Handler for bond "interfaces:" list.
* @data: ignored
*/
static gboolean
handle_bond_interfaces(NetplanParser* npp, yaml_node_t* node, __unused const void* data, GError** error)
{
/* all entries must refer to already defined IDs */
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
NetplanNetDefinition *component;
assert_type(npp, entry, YAML_SCALAR_NODE);
component = g_hash_table_lookup(npp->parsed_defs, scalar(entry));
if (!component) {
add_missing_node(npp, entry);
} else {
if (component->bridge)
return yaml_error(npp, node, error, "%s: interface '%s' is already assigned to bridge %s",
npp->current.netdef->id, scalar(entry), component->bridge);
if (component->bond && g_strcmp0(component->bond, npp->current.netdef->id) != 0)
return yaml_error(npp, node, error, "%s: interface '%s' is already assigned to bond %s",
npp->current.netdef->id, scalar(entry), component->bond);
if (!component->bond) {
component->bond = g_strdup(npp->current.netdef->id);
component->bond_link = npp->current.netdef;
}
if (component->backend == NETPLAN_BACKEND_OVS) {
g_debug("%s: Bond contains Open vSwitch interface, choosing OVS backend", npp->current.netdef->id);
npp->current.netdef->backend = NETPLAN_BACKEND_OVS;
}
}
}
return TRUE;
}
static gboolean
handle_nameservers_search(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
assert_type(npp, entry, YAML_SCALAR_NODE);
if (!npp->current.netdef->search_domains)
npp->current.netdef->search_domains = g_array_new(FALSE, FALSE, sizeof(char*));
if (!is_string_in_array(npp->current.netdef->search_domains, scalar(entry))) {
char* s = g_strdup(scalar(entry));
g_array_append_val(npp->current.netdef->search_domains, s);
} else {
g_debug("%s: Search domain '%s' has already been added", npp->current.netdef->id, scalar(entry));
}
}
mark_data_as_dirty(npp, &npp->current.netdef->search_domains);
return TRUE;
}
static gboolean
handle_nameservers_addresses(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
GArray **nameservers = NULL;
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
assert_type(npp, entry, YAML_SCALAR_NODE);
/* is it an IPv4 or IPv6 address? */
if (is_ip4_address(scalar(entry)))
nameservers = &npp->current.netdef->ip4_nameservers;
else if (is_ip6_address(scalar(entry)))
nameservers = &npp->current.netdef->ip6_nameservers;
else
return yaml_error(npp, node, error, "malformed address '%s', must be X.X.X.X or X:X:X:X:X:X:X:X", scalar(entry));
if (!(*nameservers))
*nameservers = g_array_new(FALSE, FALSE, sizeof(char*));
if (!is_string_in_array(*nameservers, scalar(entry))) {
char* s = g_strdup(scalar(entry));
g_array_append_val(*nameservers, s);
} else {
g_debug("%s: Nameserver '%s' has already been added", npp->current.netdef->id, scalar(entry));
}
}
mark_data_as_dirty(npp, &npp->current.netdef->ip4_nameservers);
mark_data_as_dirty(npp, &npp->current.netdef->ip6_nameservers);
return TRUE;
}
static gboolean
handle_link_local(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
gboolean ipv4 = FALSE;
gboolean ipv6 = FALSE;
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
assert_type(npp, entry, YAML_SCALAR_NODE);
if (g_ascii_strcasecmp(scalar(entry), "ipv4") == 0) {
ipv4 = TRUE;
mark_data_as_dirty(npp, &npp->current.netdef->linklocal.ipv4);
} else if (g_ascii_strcasecmp(scalar(entry), "ipv6") == 0) {
ipv6 = TRUE;
mark_data_as_dirty(npp, &npp->current.netdef->linklocal.ipv6);
} else
return yaml_error(npp, node, error, "invalid value for link-local: '%s'", scalar(entry));
}
npp->current.netdef->linklocal.ipv4 = ipv4;
npp->current.netdef->linklocal.ipv6 = ipv6;
return TRUE;
}
NETPLAN_ABI struct NetplanOptionalAddressType
NETPLAN_OPTIONAL_ADDRESS_TYPES[] = {
{"ipv4-ll", NETPLAN_OPTIONAL_IPV4_LL},
{"ipv6-ra", NETPLAN_OPTIONAL_IPV6_RA},
{"dhcp4", NETPLAN_OPTIONAL_DHCP4},
{"dhcp6", NETPLAN_OPTIONAL_DHCP6},
{"static", NETPLAN_OPTIONAL_STATIC},
{NULL},
};
static gboolean
handle_optional_addresses(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
assert_type(npp, entry, YAML_SCALAR_NODE);
int found = FALSE;
for (unsigned i = 0; NETPLAN_OPTIONAL_ADDRESS_TYPES[i].name != NULL; ++i) {
if (g_ascii_strcasecmp(scalar(entry), NETPLAN_OPTIONAL_ADDRESS_TYPES[i].name) == 0) {
npp->current.netdef->optional_addresses |= NETPLAN_OPTIONAL_ADDRESS_TYPES[i].flag;
found = TRUE;
break;
}
}
if (!found) {
return yaml_error(npp, node, error, "invalid value for optional-addresses: '%s'", scalar(entry));
}
}
return TRUE;
}
/* TODO: unify optional_addresses/wowlan_types, using flags */
static gboolean
handle_vxlan_flags(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
g_assert(npp->current.vxlan);
assert_type(npp, node, YAML_SEQUENCE_NODE);
yaml_node_t* key_node = node-1; // The YAML key of given sequence `node`
guint offset = GPOINTER_TO_UINT(data);
const char* const* flags = NULL;
guint flags_size = 0;
NetplanFlags* out_ptr = NULL;
switch (offset) {
case offsetof(NetplanVxlan, notifications):
out_ptr = &npp->current.vxlan->notifications;
flags = netplan_vxlan_notification_to_str;
flags_size = sizeof(netplan_vxlan_notification_to_str);
break;
case offsetof(NetplanVxlan, checksums):
out_ptr = &npp->current.vxlan->checksums;
flags = netplan_vxlan_checksum_to_str;
flags_size = sizeof(netplan_vxlan_checksum_to_str);
break;
case offsetof(NetplanVxlan, extensions):
out_ptr = &npp->current.vxlan->extensions;
flags = netplan_vxlan_extension_to_str;
flags_size = sizeof(netplan_vxlan_extension_to_str);
break;
default: g_assert_not_reached(); // LCOV_EXCL_LINE
}
g_assert(flags);
g_assert(out_ptr);
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
assert_type(npp, entry, YAML_SCALAR_NODE);
int found = FALSE;
/* Loop through the flags to find a matching string.
* Once found, shift a bit to position INDEX-1 and use bitwise OR to
* apply it to the corresponding flags field (i.e. *out_ptr) */
// The minimum flag is always 0x1 (i.e. 0b0001), so start the loop at 1.
for (unsigned j = 1; j < flags_size/sizeof(char*); ++j) {
if (g_ascii_strcasecmp(scalar(entry), flags[j]) == 0) {
*out_ptr |= 1<<(j-1);
mark_data_as_dirty(npp, out_ptr);
found = TRUE;
break;
}
}
if (!found) {
return yaml_error(npp, node, error, "invalid value for %s: '%s'",
scalar(key_node), scalar(entry));
}
}
return TRUE;
}
static gboolean
handle_vxlan_guint(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
g_assert(npp->current.vxlan);
return handle_generic_guint(npp, node, npp->current.vxlan, data, error);
}
static gboolean
handle_vxlan_tristate(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
g_assert(npp->current.vxlan);
return handle_generic_tristate(npp, node, npp->current.vxlan, data, error);
}
static int
get_ip_family(const char* address)
{
g_autofree char *ip_str;
char *prefix_len;
ip_str = g_strdup(address);
prefix_len = strrchr(ip_str, '/');
if (prefix_len)
*prefix_len = '\0';
if (is_ip4_address(ip_str))
return AF_INET;
if (is_ip6_address(ip_str))
return AF_INET6;
return -1;
}
static gboolean
check_and_set_family(gint family, gint* dest)
{
if (*dest != -1 && *dest != family)
return FALSE;
*dest = family;
return TRUE;
}
/* TODO: (cyphermox) Refactor the functions below. There's a lot of room for reuse. */
static gboolean
handle_routes_bool(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
g_assert(npp->current.route);
return handle_generic_bool(npp, node, npp->current.route, data, error);
}
static gboolean
handle_routes_scope(NetplanParser* npp, yaml_node_t* node, __unused const void* data, GError** error)
{
NetplanIPRoute* route = npp->current.route;
if (route->scope)
g_free(route->scope);
route->scope = g_strdup(scalar(node));
if (g_ascii_strcasecmp(route->scope, "global") == 0 ||
g_ascii_strcasecmp(route->scope, "link") == 0 ||
g_ascii_strcasecmp(route->scope, "host") == 0)
return TRUE;
return yaml_error(npp, node, error, "invalid route scope '%s'", route->scope);
}
static gboolean
handle_routes_type(NetplanParser* npp, yaml_node_t* node, __unused const void* data, GError** error)
{
NetplanIPRoute* route = npp->current.route;
if (route->type)
g_free(route->type);
route->type = g_strdup(scalar(node));
/* local, broadcast, anycast, multicast, nat and xresolve are supported
* since systemd-networkd v243 */
/* keep "unicast" default at position 1 */
if ( g_ascii_strcasecmp(route->type, "unicast") == 0
|| g_ascii_strcasecmp(route->type, "anycast") == 0
|| g_ascii_strcasecmp(route->type, "blackhole") == 0
|| g_ascii_strcasecmp(route->type, "broadcast") == 0
|| g_ascii_strcasecmp(route->type, "local") == 0
|| g_ascii_strcasecmp(route->type, "multicast") == 0
|| g_ascii_strcasecmp(route->type, "nat") == 0
|| g_ascii_strcasecmp(route->type, "prohibit") == 0
|| g_ascii_strcasecmp(route->type, "throw") == 0
|| g_ascii_strcasecmp(route->type, "unreachable") == 0
|| g_ascii_strcasecmp(route->type, "xresolve") == 0)
return TRUE;
return yaml_error(npp, node, error, "invalid route type '%s'", route->type);
}
static gboolean
handle_routes_ip(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
NetplanIPRoute* route = npp->current.route;
guint offset = GPOINTER_TO_UINT(data);
int family = get_ip_family(scalar(node));
char** dest = (char**) ((void*) route + offset);
if (family < 0)
return yaml_error(npp, node, error, "invalid IP family '%d'", family);
if (!check_and_set_family(family, &route->family))
return yaml_error(npp, node, error, "IP family mismatch in route to %s", scalar(node));
g_free(*dest);
*dest = g_strdup(scalar(node));
mark_data_as_dirty(npp, dest);
return TRUE;
}
static gboolean
handle_routes_destination(NetplanParser *npp, yaml_node_t *node, __unused const void *data, GError **error)
{
const char *addr = scalar(node);
if (g_strcmp0(addr, "default") != 0) /* netplan-feature: default-routes */
return handle_routes_ip(npp, node, route_offset(to), error);
set_str_if_null(npp->current.route->to, addr);
return TRUE;
}
static gboolean
handle_ip_rule_ip(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
NetplanIPRule* ip_rule = npp->current.ip_rule;
guint offset = GPOINTER_TO_UINT(data);
int family = get_ip_family(scalar(node));
char** dest = (char**) ((void*) ip_rule + offset);
if (family < 0)
return yaml_error(npp, node, error, "invalid IP family '%d'", family);
if (!check_and_set_family(family, &ip_rule->family))
return yaml_error(npp, node, error, "IP family mismatch in route to %s", scalar(node));
g_free(*dest);
*dest = g_strdup(scalar(node));
mark_data_as_dirty(npp, dest);
return TRUE;
}
static gboolean
handle_ip_rule_guint(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
g_assert(npp->current.ip_rule);
return handle_generic_guint(npp, node, npp->current.ip_rule, data, error);
}
static gboolean
handle_routes_guint(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
g_assert(npp->current.route);
return handle_generic_guint(npp, node, npp->current.route, data, error);
}
static gboolean
handle_ip_rule_tos(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
NetplanIPRule* ip_rule = npp->current.ip_rule;
gboolean ret = handle_generic_guint(npp, node, ip_rule, data, error);
if (ip_rule->tos > 255)
return yaml_error(npp, node, error, "invalid ToS (must be between 0 and 255): %s", scalar(node));
return ret;
}
/****************************************************
* Grammar and handlers for network config "bridge_params" entry
****************************************************/
static gboolean
handle_bridge_path_cost(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const void* data, GError** error)
{
for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) {
yaml_node_t* key, *value;
guint v;
gchar* endptr;
NetplanNetDefinition *component;
guint* ref_ptr;
key = yaml_document_get_node(&npp->doc, entry->key);
assert_type(npp, key, YAML_SCALAR_NODE);
value = yaml_document_get_node(&npp->doc, entry->value);
assert_type(npp, value, YAML_SCALAR_NODE);
if (key_prefix && npp->null_fields) {
g_autofree char* full_key = NULL;
full_key = g_strdup_printf("%s\t%s", key_prefix, key->data.scalar.value);
if (g_hash_table_contains(npp->null_fields, full_key))
continue;
}
component = g_hash_table_lookup(npp->parsed_defs, scalar(key));
if (!component) {
add_missing_node(npp, key);
} else {
ref_ptr = ((guint*) ((void*) component + GPOINTER_TO_UINT(data)));
if (*ref_ptr)
return yaml_error(npp, node, error, "%s: interface '%s' already has a path cost of %u",
npp->current.netdef->id, scalar(key), *ref_ptr);
v = g_ascii_strtoull(scalar(value), &endptr, 10);
if (*endptr != '\0')
return yaml_error(npp, node, error, "invalid unsigned int value '%s'", scalar(value));
g_debug("%s: adding path '%s' of cost: %d", npp->current.netdef->id, scalar(key), v);
*ref_ptr = v;
mark_data_as_dirty(npp, ref_ptr);
}
}
return TRUE;
}
static gboolean
handle_bridge_port_priority(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const void* data, GError** error)
{
for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) {
yaml_node_t* key, *value;
guint v;
gchar* endptr;
NetplanNetDefinition *component;
guint* ref_ptr;
key = yaml_document_get_node(&npp->doc, entry->key);
assert_type(npp, key, YAML_SCALAR_NODE);
value = yaml_document_get_node(&npp->doc, entry->value);
assert_type(npp, value, YAML_SCALAR_NODE);
if (key_prefix && npp->null_fields) {
g_autofree char* full_key = NULL;
full_key = g_strdup_printf("%s\t%s", key_prefix, key->data.scalar.value);
if (g_hash_table_contains(npp->null_fields, full_key))
continue;
}
component = g_hash_table_lookup(npp->parsed_defs, scalar(key));
if (!component) {
add_missing_node(npp, key);
} else {
ref_ptr = ((guint*) ((void*) component + GPOINTER_TO_UINT(data)));
if (*ref_ptr)
return yaml_error(npp, node, error, "%s: interface '%s' already has a port priority of %u",
npp->current.netdef->id, scalar(key), *ref_ptr);
v = g_ascii_strtoull(scalar(value), &endptr, 10);
if (*endptr != '\0' || v > 63)
return yaml_error(npp, node, error, "invalid port priority value (must be between 0 and 63): %s",
scalar(value));
g_debug("%s: adding port '%s' of priority: %d", npp->current.netdef->id, scalar(key), v);
*ref_ptr = v;
mark_data_as_dirty(npp, ref_ptr);
}
}
return TRUE;
}
static const mapping_entry_handler bridge_params_handlers[] = {
{"ageing-time", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bridge_params.ageing_time)},
{"aging-time", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bridge_params.ageing_time)},
{"forward-delay", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bridge_params.forward_delay)},
{"hello-time", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bridge_params.hello_time)},
{"max-age", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bridge_params.max_age)},
{"path-cost", YAML_MAPPING_NODE, {.map={.custom=handle_bridge_path_cost}}, netdef_offset(bridge_params.path_cost)},
{"port-priority", YAML_MAPPING_NODE, {.map={.custom=handle_bridge_port_priority}}, netdef_offset(bridge_params.port_priority)},
{"priority", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(bridge_params.priority)},
{"stp", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(bridge_params.stp)},
{NULL}
};
static gboolean
handle_bridge(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, __unused const void* _, GError** error)
{
npp->current.netdef->custom_bridging = TRUE;
npp->current.netdef->bridge_params.stp = TRUE;
return process_mapping(npp, node, key_prefix, bridge_params_handlers, NULL, error);
}
/****************************************************
* Grammar and handlers for network config "routes" entry
****************************************************/
static const mapping_entry_handler routes_handlers[] = {
{"from", YAML_SCALAR_NODE, {.generic=handle_routes_ip}, route_offset(from)},
{"on-link", YAML_SCALAR_NODE, {.generic=handle_routes_bool}, route_offset(onlink)},
{"scope", YAML_SCALAR_NODE, {.generic=handle_routes_scope}, NULL},
{"table", YAML_SCALAR_NODE, {.generic=handle_routes_guint}, route_offset(table)},
{"to", YAML_SCALAR_NODE, {.generic=handle_routes_destination}, NULL},
{"type", YAML_SCALAR_NODE, {.generic=handle_routes_type}, NULL},
{"via", YAML_SCALAR_NODE, {.generic=handle_routes_ip}, route_offset(via)},
{"metric", YAML_SCALAR_NODE, {.generic=handle_routes_guint}, route_offset(metric)},
{"mtu", YAML_SCALAR_NODE, {.generic=handle_routes_guint}, route_offset(mtubytes)},
{"congestion-window", YAML_SCALAR_NODE, {.generic=handle_routes_guint}, route_offset(congestion_window)},
{"advertised-receive-window", YAML_SCALAR_NODE, {.generic=handle_routes_guint}, route_offset(advertised_receive_window)},
{NULL}
};
static gboolean
handle_routes(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
if (!npp->current.netdef->routes)
npp->current.netdef->routes = g_array_new(FALSE, TRUE, sizeof(NetplanIPRoute*));
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
NetplanIPRoute* route;
assert_type(npp, entry, YAML_MAPPING_NODE);
g_assert(npp->current.route == NULL);
route = g_new0(NetplanIPRoute, 1);
route->type = g_strdup("unicast");
route->scope = NULL;
route->family = -1; /* 0 is a valid family ID */
route->metric = NETPLAN_METRIC_UNSPEC; /* 0 is a valid metric */
route->table = NETPLAN_ROUTE_TABLE_UNSPEC;
g_debug("%s: adding new route", npp->current.netdef->id);
npp->current.route = route;
if (!process_mapping(npp, entry, NULL, routes_handlers, NULL, error))
goto err;
/* Set the default scope, according to type */
if (!route->scope) {
if ( g_ascii_strcasecmp(route->type, "local") == 0
|| g_ascii_strcasecmp(route->type, "nat") == 0)
route->scope = (g_strdup("host"));
/* Non-gatewayed unicast routes are scope:link, too */
else if ( (g_ascii_strcasecmp(route->type, "unicast") == 0 && !route->via)
|| g_ascii_strcasecmp(route->type, "broadcast") == 0
|| g_ascii_strcasecmp(route->type, "multicast") == 0
|| g_ascii_strcasecmp(route->type, "anycast") == 0)
route->scope = g_strdup("link");
else
route->scope = g_strdup("global");
}
if ( ( g_ascii_strcasecmp(route->scope, "link") == 0
|| g_ascii_strcasecmp(route->scope, "host") == 0)
&& !route->to) {
yaml_error(npp, node, error, "link and host routes must specify a 'to' IP");
goto err;
} else if ( g_ascii_strcasecmp(route->type, "unicast") == 0
&& g_ascii_strcasecmp(route->scope, "global") == 0
&& (!route->to || !route->via)) {
yaml_error(npp, node, error, "global unicast route must include both a 'to' and 'via' IP");
goto err;
} else if (g_ascii_strcasecmp(route->type, "unicast") != 0 && !route->to) {
yaml_error(npp, node, error, "non-unicast routes must specify a 'to' IP");
goto err;
}
if (is_route_present(npp->current.netdef, route)) {
g_debug("%s: route (to: %s, via: %s, table: %d, metric: %d) has already been added",
npp->current.netdef->id,
route->to,
route->via,
route->table,
route->metric);
route_clear(&npp->current.route);
npp->current.route = NULL;
continue;
}
g_array_append_val(npp->current.netdef->routes, route);
npp->current.route = NULL;
}
mark_data_as_dirty(npp, &npp->current.netdef->routes);
return TRUE;
err:
route_clear(&npp->current.route);
npp->current.route = NULL;
return FALSE;
}
static const mapping_entry_handler ip_rules_handlers[] = {
{"from", YAML_SCALAR_NODE, {.generic=handle_ip_rule_ip}, ip_rule_offset(from)},
{"mark", YAML_SCALAR_NODE, {.generic=handle_ip_rule_guint}, ip_rule_offset(fwmark)},
{"priority", YAML_SCALAR_NODE, {.generic=handle_ip_rule_guint}, ip_rule_offset(priority)},
{"table", YAML_SCALAR_NODE, {.generic=handle_ip_rule_guint}, ip_rule_offset(table)},
{"to", YAML_SCALAR_NODE, {.generic=handle_ip_rule_ip}, ip_rule_offset(to)},
{"type-of-service", YAML_SCALAR_NODE, {.generic=handle_ip_rule_tos}, ip_rule_offset(tos)},
{NULL}
};
static gboolean
handle_ip_rules(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
gboolean ret;
NetplanIPRule* ip_rule = g_new0(NetplanIPRule, 1);
reset_ip_rule(ip_rule);
npp->current.ip_rule = ip_rule;
ret = process_mapping(npp, entry, NULL, ip_rules_handlers, NULL, error);
npp->current.ip_rule = NULL;
if (ret && !ip_rule->from && !ip_rule->to)
ret = yaml_error(npp, node, error, "IP routing policy must include either a 'from' or 'to' IP");
if (!ret) {
ip_rule_clear(&ip_rule);
return FALSE;
}
if (!npp->current.netdef->ip_rules)
npp->current.netdef->ip_rules = g_array_new(FALSE, FALSE, sizeof(NetplanIPRule*));
if (is_route_rule_present(npp->current.netdef, ip_rule)) {
g_debug("%s: rule (from: %s, to: %s, table: %d) has already been added",
npp->current.netdef->id,
ip_rule->from,
ip_rule->to,
ip_rule->table);
ip_rule_clear(&ip_rule);
npp->current.ip_rule = NULL;
continue;
}
g_array_append_val(npp->current.netdef->ip_rules, ip_rule);
}
mark_data_as_dirty(npp, &npp->current.netdef->ip_rules);
return TRUE;
}
/****************************************************
* Grammar and handlers for bond parameters
****************************************************/
static gboolean
handle_arp_ip_targets(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
if (!npp->current.netdef->bond_params.arp_ip_targets) {
npp->current.netdef->bond_params.arp_ip_targets = g_array_new(FALSE, FALSE, sizeof(char *));
}
/* Avoid adding the same arp_ip_targets in a 2nd parsing pass by comparing
* the array size to the YAML sequence size. Skip if they are equal. */
guint item_count = node->data.sequence.items.top - node->data.sequence.items.start;
if (npp->current.netdef->bond_params.arp_ip_targets->len == item_count) {
g_debug("%s: all arp ip targets have already been added", npp->current.netdef->id);
return TRUE;
}
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
g_autofree char* addr = NULL;
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
assert_type(npp, entry, YAML_SCALAR_NODE);
addr = g_strdup(scalar(entry));
/* is it an IPv4 address? */
if (is_ip4_address(addr)) {
char* s = g_strdup(scalar(entry));
g_array_append_val(npp->current.netdef->bond_params.arp_ip_targets, s);
continue;
}
return yaml_error(npp, node, error, "malformed address '%s', must be X.X.X.X or X:X:X:X:X:X:X:X", scalar(entry));
}
mark_data_as_dirty(npp, &npp->current.netdef->bond_params.arp_ip_targets);
return TRUE;
}
static gboolean
handle_bond_primary_member(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
NetplanNetDefinition *component;
char** ref_ptr;
component = g_hash_table_lookup(npp->parsed_defs, scalar(node));
if (!component) {
add_missing_node(npp, node);
} else {
/* If this is not the primary pass, the primary member might already be equally set. */
if (!g_strcmp0(npp->current.netdef->bond_params.primary_member, scalar(node))) {
return TRUE;
} else if (npp->current.netdef->bond_params.primary_member)
return yaml_error(npp, node, error, "%s: bond already has a primary member: %s",
npp->current.netdef->id, npp->current.netdef->bond_params.primary_member);
ref_ptr = ((char**) ((void*) component + GPOINTER_TO_UINT(data)));
*ref_ptr = g_strdup(scalar(node));
npp->current.netdef->bond_params.primary_member = g_strdup(scalar(node));
mark_data_as_dirty(npp, ref_ptr);
}
mark_data_as_dirty(npp, &npp->current.netdef->bond_params.primary_member);
return TRUE;
}
static gboolean
handle_bond_lacp_rate(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
if (!(strcmp(scalar(node), "slow") == 0 || strcmp(scalar(node), "fast") == 0))
return yaml_error(npp, node, error, "unknown lacp-rate value '%s' (expected 'fast' or 'slow')", scalar(node));
return handle_netdef_str(npp, node, data, error);
}
static const mapping_entry_handler bond_params_handlers[] = {
{"mode", YAML_SCALAR_NODE, {.generic=handle_bond_mode}, netdef_offset(bond_params.mode)},
{"lacp-rate", YAML_SCALAR_NODE, {.generic=handle_bond_lacp_rate}, netdef_offset(bond_params.lacp_rate)},
{"mii-monitor-interval", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bond_params.monitor_interval)},
{"min-links", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(bond_params.min_links)},
{"transmit-hash-policy", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bond_params.transmit_hash_policy)},
{"ad-select", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bond_params.selection_logic)},
{"all-slaves-active", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(bond_params.all_members_active)}, /* wokeignore:rule=slave */
{"all-members-active", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(bond_params.all_members_active)},
{"arp-interval", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bond_params.arp_interval)},
/* TODO: arp_ip_targets */
{"arp-ip-targets", YAML_SEQUENCE_NODE, {.generic=handle_arp_ip_targets}, NULL},
{"arp-validate", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bond_params.arp_validate)},
{"arp-all-targets", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bond_params.arp_all_targets)},
{"up-delay", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bond_params.up_delay)},
{"down-delay", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bond_params.down_delay)},
{"fail-over-mac-policy", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bond_params.fail_over_mac_policy)},
{"gratuitous-arp", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(bond_params.gratuitous_arp)},
/* Handle the old misspelling */
{"gratuitious-arp", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(bond_params.gratuitous_arp)},
/* TODO: unsolicited_na */
{"packets-per-slave", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(bond_params.packets_per_member)}, /* wokeignore:rule=slave */
{"packets-per-member", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(bond_params.packets_per_member)},
{"primary-reselect-policy", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bond_params.primary_reselect_policy)},
{"resend-igmp", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(bond_params.resend_igmp)},
{"learn-packet-interval", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(bond_params.learn_interval)},
{"primary", YAML_SCALAR_NODE, {.generic=handle_bond_primary_member}, netdef_offset(bond_params.primary_member)},
{NULL}
};
static gboolean
handle_vrf_interfaces(NetplanParser* npp, yaml_node_t* node, __unused const void* data, GError** error)
{
/* all entries must refer to already defined IDs */
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
NetplanNetDefinition *component;
assert_type(npp, entry, YAML_SCALAR_NODE);
component = g_hash_table_lookup(npp->parsed_defs, scalar(entry));
if (!component) {
add_missing_node(npp, entry);
} else {
if (component->vrf_link && component->vrf_link != npp->current.netdef)
return yaml_error(npp, node, error, "%s: interface '%s' is already assigned to vrf %s",
npp->current.netdef->id, scalar(entry), component->vrf_link->id);
component->vrf_link = npp->current.netdef;
}
}
return TRUE;
}
static gboolean
handle_vxlan_source_port(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
assert_type(npp, node, YAML_SEQUENCE_NODE);
if (node->data.sequence.items.top - node->data.sequence.items.start != 2)
return yaml_error(npp, node, error, "%s: Expected exactly two values for port-range",
npp->current.netdef->id);
yaml_node_t* itm1 = yaml_document_get_node(&npp->doc, *node->data.sequence.items.start);
yaml_node_t* itm2 = yaml_document_get_node(&npp->doc, *node->data.sequence.items.start+1);
if (!handle_generic_guint(npp, itm1, npp->current.vxlan, vxlan_offset(source_port_min), error))
return FALSE;
if (!handle_generic_guint(npp, itm2, npp->current.vxlan, vxlan_offset(source_port_max), error))
return FALSE;
guint tmp = 0;
if (npp->current.netdef->vxlan->source_port_min > npp->current.netdef->vxlan->source_port_max) {
tmp = npp->current.netdef->vxlan->source_port_min;
npp->current.netdef->vxlan->source_port_min = npp->current.netdef->vxlan->source_port_max;
npp->current.netdef->vxlan->source_port_max = tmp;
g_warning("%s: swapped invalid port-range order [MIN, MAX]", npp->current.netdef->id);
}
return TRUE;
}
static gboolean
handle_bonding(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, __unused const void* _, GError** error)
{
return process_mapping(npp, node, key_prefix, bond_params_handlers, NULL, error);
}
static gboolean
handle_dhcp_identifier(NetplanParser* npp, yaml_node_t* node, __unused const void* data, GError** error)
{
g_free(npp->current.netdef->dhcp_identifier);
/* "duid" is the default case, so we don't store it. */
if (g_ascii_strcasecmp(scalar(node), "duid") != 0)
npp->current.netdef->dhcp_identifier = g_strdup(scalar(node));
else
npp->current.netdef->dhcp_identifier = NULL;
if (npp->current.netdef->dhcp_identifier == NULL ||
g_ascii_strcasecmp(npp->current.netdef->dhcp_identifier, "mac") == 0)
return TRUE;
return yaml_error(npp, node, error, "invalid DHCP client identifier type '%s'", npp->current.netdef->dhcp_identifier);
}
/****************************************************
* Grammar and handlers for tunnels
****************************************************/
static gboolean
handle_tunnel_addr(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
g_autofree char* addr = NULL;
char* prefix_len;
/* split off /prefix_len */
addr = g_strdup(scalar(node));
prefix_len = strrchr(addr, '/');
if (prefix_len)
return yaml_error(npp, node, error, "address '%s' should not include /prefixlength", scalar(node));
/* is it an IPv4 address? */
if (is_ip4_address(addr))
return handle_netdef_ip4(npp, node, data, error);
/* is it an IPv6 address? */
if (is_ip6_address(addr))
return handle_netdef_ip6(npp, node, data, error);
return yaml_error(npp, node, error, "malformed address '%s', must be X.X.X.X or X:X:X:X:X:X:X:X", scalar(node));
}
static gboolean
handle_tunnel_mode(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
const char *key = scalar(node);
NetplanTunnelMode i;
// Skip over unknown (0) tunnel mode.
for (i = 1; i < NETPLAN_TUNNEL_MODE_MAX_; ++i) {
if (g_strcmp0(netplan_tunnel_mode_name(i), key) == 0) {
npp->current.netdef->tunnel.mode = i;
return TRUE;
}
}
return yaml_error(npp, node, error, "%s: tunnel mode '%s' is not supported", npp->current.netdef->id, key);
}
static const mapping_entry_handler tunnel_keys_handlers[] = {
{"input", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(tunnel.input_key)},
{"output", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(tunnel.output_key)},
{"private", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(tunnel.private_key)},
{"private-key-flags", YAML_SEQUENCE_NODE, {.generic=handle_tunnel_key_flags}, NULL},
{NULL}
};
static gboolean
handle_tunnel_key_mapping(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, __unused const void* _, GError** error)
{
gboolean ret = FALSE;
/* We overload the 'key[s]' setting for tunnels; such that it can either be a
* single scalar with the same key to use for both input, output and private
* keys, or a mapping where one can specify each. */
if (node->type == YAML_SCALAR_NODE) {
ret = handle_netdef_str(npp, node, netdef_offset(tunnel.input_key), error);
if (ret)
ret = handle_netdef_str(npp, node, netdef_offset(tunnel.output_key), error);
if (ret)
ret = handle_netdef_str(npp, node, netdef_offset(tunnel.private_key), error);
} else if (node->type == YAML_MAPPING_NODE)
ret = process_mapping(npp, node, key_prefix, tunnel_keys_handlers, NULL, error);
else
return yaml_error(npp, node, error, "invalid type for 'key[s]': must be a scalar or mapping");
return ret;
}
/**
* Handler for setting a NetplanWireguardPeer string field from a scalar node
* @data: pointer to the const char* field to write
*/
static gboolean
handle_wireguard_peer_str(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
g_assert(npp->current.wireguard_peer);
return handle_generic_str(npp, node, npp->current.wireguard_peer, data, error);
}
/**
* Handler for setting a NetplanWireguardPeer string field from a scalar node
* @data: pointer to the guint field to write
*/
static gboolean
handle_wireguard_peer_guint(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
g_assert(npp->current.wireguard_peer);
return handle_generic_guint(npp, node, npp->current.wireguard_peer, data, error);
}
static gboolean
handle_wireguard_allowed_ips(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
return handle_generic_addresses(npp, node, FALSE, &(npp->current.wireguard_peer->allowed_ips),
&(npp->current.wireguard_peer->allowed_ips), error);
}
static gboolean
handle_wireguard_endpoint(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
g_autofree char* endpoint = NULL;
char* port;
char* address;
guint64 port_num;
/* If endpoint is an empty string just ignore it */
if (!g_strcmp0(scalar(node), "")) {
return TRUE;
}
endpoint = g_strdup(scalar(node));
/* absolute minimal length of endpoint is 3 chars: 'h:8' */
if (strlen(endpoint) < 3) {
return yaml_error(npp, node, error, "invalid endpoint address or hostname '%s'", scalar(node));
}
if (endpoint[0] == '[') {
/* this is an ipv6 endpoint in [ad:rr:ee::ss]:port form */
char *endbrace = strrchr(endpoint, ']');
if (!endbrace)
return yaml_error(npp, node, error, "invalid address in endpoint '%s'", scalar(node));
address = endpoint + 1;
*endbrace = '\0';
port = strrchr(endbrace + 1, ':');
} else {
address = endpoint;
port = strrchr(endpoint, ':');
}
/* split off :port */
if (!port)
return yaml_error(npp, node, error, "endpoint '%s' is missing :port", scalar(node));
*port = '\0';
port++; /* skip former ':' into first char of port */
port_num = g_ascii_strtoull(port, NULL, 10);
if (port_num > 65535)
return yaml_error(npp, node, error, "invalid port in endpoint '%s'", scalar(node));
if (is_ip4_address(address) || is_ip6_address(address) || is_hostname(address)) {
return handle_wireguard_peer_str(npp, node, wireguard_peer_offset(endpoint), error);
}
return yaml_error(npp, node, error, "invalid endpoint address or hostname '%s'", scalar(node));
}
static const mapping_entry_handler wireguard_peer_keys_handlers[] = {
{"public", YAML_SCALAR_NODE, {.generic=handle_wireguard_peer_str}, wireguard_peer_offset(public_key)},
{"shared", YAML_SCALAR_NODE, {.generic=handle_wireguard_peer_str}, wireguard_peer_offset(preshared_key)},
{NULL}
};
static gboolean
handle_wireguard_peer_key_mapping(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, __unused const void* _, GError** error)
{
return process_mapping(npp, node, key_prefix, wireguard_peer_keys_handlers, NULL, error);
}
const mapping_entry_handler wireguard_peer_handlers[] = {
{"keys", YAML_MAPPING_NODE, {.map={.custom=handle_wireguard_peer_key_mapping}}, NULL},
{"keepalive", YAML_SCALAR_NODE, {.generic=handle_wireguard_peer_guint}, wireguard_peer_offset(keepalive)},
{"endpoint", YAML_SCALAR_NODE, {.generic=handle_wireguard_endpoint}, NULL},
{"allowed-ips", YAML_SEQUENCE_NODE, {.generic=handle_wireguard_allowed_ips}, NULL},
{NULL}
};
static gboolean
handle_wireguard_peers(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
if (!npp->current.netdef->wireguard_peers)
npp->current.netdef->wireguard_peers = g_array_new(FALSE, TRUE, sizeof(NetplanWireguardPeer*));
/* Avoid adding the same peers in a 2nd parsing pass by comparing
* the array size to the YAML sequence size. Skip if they are equal. */
guint item_count = node->data.sequence.items.top - node->data.sequence.items.start;
if (npp->current.netdef->wireguard_peers->len == item_count) {
g_debug("%s: all wireguard peers have already been added", npp->current.netdef->id);
return TRUE;
}
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
assert_type(npp, entry, YAML_MAPPING_NODE);
g_assert(npp->current.wireguard_peer == NULL);
npp->current.wireguard_peer = g_new0(NetplanWireguardPeer, 1);
npp->current.wireguard_peer->allowed_ips = g_array_new(FALSE, FALSE, sizeof(char*));
g_debug("%s: adding new wireguard peer", npp->current.netdef->id);
if (!process_mapping(npp, entry, NULL, wireguard_peer_handlers, NULL, error)) {
wireguard_peer_clear(&npp->current.wireguard_peer);
npp->current.wireguard_peer = NULL;
return FALSE;
}
g_array_append_val(npp->current.netdef->wireguard_peers, npp->current.wireguard_peer);
npp->current.wireguard_peer = NULL;
}
return TRUE;
}
/****************************************************
* Grammar and handlers for network devices
****************************************************/
static gboolean
handle_ovs_bond_lacp(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
if (npp->current.netdef->type != NETPLAN_DEF_TYPE_BOND)
return yaml_error(npp, node, error, "Key 'lacp' is only valid for interface type 'Open vSwitch bond'");
if (g_strcmp0(scalar(node), "active") && g_strcmp0(scalar(node), "passive") && g_strcmp0(scalar(node), "off"))
return yaml_error(npp, node, error, "Value of 'lacp' needs to be 'active', 'passive' or 'off");
return handle_netdef_str(npp, node, data, error);
}
static gboolean
handle_ovs_bridge_bool(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
if (npp->current.netdef->type != NETPLAN_DEF_TYPE_BRIDGE)
return yaml_error(npp, node, error, "Key is only valid for interface type 'Open vSwitch bridge'");
return handle_netdef_bool(npp, node, data, error);
}
static gboolean
handle_ovs_bridge_fail_mode(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
if (npp->current.netdef->type != NETPLAN_DEF_TYPE_BRIDGE)
return yaml_error(npp, node, error, "Key 'fail-mode' is only valid for interface type 'Open vSwitch bridge'");
if (g_strcmp0(scalar(node), "standalone") && g_strcmp0(scalar(node), "secure"))
return yaml_error(npp, node, error, "Value of 'fail-mode' needs to be 'standalone' or 'secure'");
return handle_netdef_str(npp, node, data, error);
}
static gboolean
handle_ovs_protocol(NetplanParser* npp, yaml_node_t* node, void* entryptr, const void* data, GError** error)
{
const char* deprecated[] = { "OpenFlow16" };
const char* supported[] = {
"OpenFlow10", "OpenFlow11", "OpenFlow12", "OpenFlow13", "OpenFlow14", "OpenFlow15", NULL
};
unsigned i = 0;
guint offset = GPOINTER_TO_UINT(data);
GArray** protocols = (GArray**) ((void*) entryptr + offset);
for (yaml_node_item_t *iter = node->data.sequence.items.start; iter < node->data.sequence.items.top; iter++) {
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *iter);
assert_type(npp, entry, YAML_SCALAR_NODE);
if (!g_strcmp0(scalar(entry), deprecated[0])) {
g_warning("Open vSwitch: Ignoring deprecated protocol: %s", scalar(entry));
continue;
}
for (i = 0; supported[i] != NULL; ++i)
if (!g_strcmp0(scalar(entry), supported[i]))
break;
if (supported[i] == NULL)
return yaml_error(npp, node, error, "Unsupported OVS 'protocol' value: %s", scalar(entry));
if (!*protocols)
*protocols = g_array_new(FALSE, FALSE, sizeof(char*));
/* Do not insert the same address twice in the list */
if (!is_string_in_array(*protocols, scalar(entry))) {
char* s = g_strdup(scalar(entry));
g_array_append_val(*protocols, s);
}
}
return TRUE;
}
static gboolean
handle_ovs_bridge_protocol(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
if (npp->current.netdef->type != NETPLAN_DEF_TYPE_BRIDGE)
return yaml_error(npp, node, error, "Key 'protocols' is only valid for interface type 'Open vSwitch bridge'");
return handle_ovs_protocol(npp, node, npp->current.netdef, data, error);
}
static gboolean
handle_ovs_bridge_controller_connection_mode(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
if (npp->current.netdef->type != NETPLAN_DEF_TYPE_BRIDGE)
return yaml_error(npp, node, error, "Key 'controller.connection-mode' is only valid for interface type 'Open vSwitch bridge'");
if (g_strcmp0(scalar(node), "in-band") && g_strcmp0(scalar(node), "out-of-band"))
return yaml_error(npp, node, error, "Value of 'connection-mode' needs to be 'in-band' or 'out-of-band'");
return handle_netdef_str(npp, node, data, error);
}
static gboolean
handle_ovs_bridge_controller_addresses(NetplanParser* npp, yaml_node_t* node, __unused const void* data, GError** error)
{
if (npp->current.netdef->type != NETPLAN_DEF_TYPE_BRIDGE)
return yaml_error(npp, node, error, "Key 'controller.addresses' is only valid for interface type 'Open vSwitch bridge'");
for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) {
gchar** vec = NULL;
gboolean is_host = FALSE;
gboolean is_port = FALSE;
gboolean is_unix = FALSE;
yaml_node_t *entry = yaml_document_get_node(&npp->doc, *i);
assert_type(npp, entry, YAML_SCALAR_NODE);
/* We always need at least one colon */
if (!g_strrstr(scalar(entry), ":"))
return yaml_error(npp, node, error, "Unsupported OVS controller target: %s", scalar(entry));
vec = g_strsplit (scalar(entry), ":", 2);
is_host = !g_strcmp0(vec[0], "tcp") || !g_strcmp0(vec[0], "ssl");
is_port = !g_strcmp0(vec[0], "ptcp") || !g_strcmp0(vec[0], "pssl");
is_unix = !g_strcmp0(vec[0], "unix") || !g_strcmp0(vec[0], "punix");
if (!npp->current.netdef->ovs_settings.controller.addresses)
npp->current.netdef->ovs_settings.controller.addresses = g_array_new(FALSE, FALSE, sizeof(char*));
/* Do not insert the same address twice in the list */
if (is_string_in_array(npp->current.netdef->ovs_settings.controller.addresses, scalar(entry))) {
g_strfreev(vec);
continue;
}
/* Format: [p]unix:file */
if (is_unix && vec[1] != NULL && vec[2] == NULL) {
char* s = g_strdup(scalar(entry));
g_array_append_val(npp->current.netdef->ovs_settings.controller.addresses, s);
g_strfreev(vec);
continue;
/* Format tcp:host[:port] or ssl:host[:port] */
} else if (is_host && validate_ovs_target(TRUE, vec[1])) {
char* s = g_strdup(scalar(entry));
g_array_append_val(npp->current.netdef->ovs_settings.controller.addresses, s);
g_strfreev(vec);
continue;
/* Format ptcp:[port][:host] or pssl:[port][:host] */
} else if (is_port && validate_ovs_target(FALSE, vec[1])) {
char* s = g_strdup(scalar(entry));
g_array_append_val(npp->current.netdef->ovs_settings.controller.addresses, s);
g_strfreev(vec);
continue;
}
g_strfreev(vec);
return yaml_error(npp, node, error, "Unsupported OVS controller target: %s", scalar(entry));
}
return TRUE;
}
static const mapping_entry_handler ovs_controller_handlers[] = {
{"addresses", YAML_SEQUENCE_NODE, {.generic=handle_ovs_bridge_controller_addresses}, netdef_offset(ovs_settings.controller.addresses)},
{"connection-mode", YAML_SCALAR_NODE, {.generic=handle_ovs_bridge_controller_connection_mode}, netdef_offset(ovs_settings.controller.connection_mode)},
{NULL},
};
static const mapping_entry_handler ovs_backend_settings_handlers[] = {
{"external-ids", YAML_MAPPING_NODE, {.map={.custom=handle_netdef_map}}, netdef_offset(ovs_settings.external_ids)},
{"other-config", YAML_MAPPING_NODE, {.map={.custom=handle_netdef_map}}, netdef_offset(ovs_settings.other_config)},
{"lacp", YAML_SCALAR_NODE, {.generic=handle_ovs_bond_lacp}, netdef_offset(ovs_settings.lacp)},
{"fail-mode", YAML_SCALAR_NODE, {.generic=handle_ovs_bridge_fail_mode}, netdef_offset(ovs_settings.fail_mode)},
{"mcast-snooping", YAML_SCALAR_NODE, {.generic=handle_ovs_bridge_bool}, netdef_offset(ovs_settings.mcast_snooping)},
{"rstp", YAML_SCALAR_NODE, {.generic=handle_ovs_bridge_bool}, netdef_offset(ovs_settings.rstp)},
{"protocols", YAML_SEQUENCE_NODE, {.generic=handle_ovs_bridge_protocol}, netdef_offset(ovs_settings.protocols)},
{"controller", YAML_MAPPING_NODE, {.map={.handlers=ovs_controller_handlers}}, NULL},
{NULL}
};
static gboolean
handle_ovs_backend(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, __unused const void* _, GError** error)
{
GList* values = NULL;
gboolean ret = process_mapping(npp, node, key_prefix, ovs_backend_settings_handlers, &values, error);
guint len = g_list_length(values);
if (npp->current.netdef->type != NETPLAN_DEF_TYPE_BOND && npp->current.netdef->type != NETPLAN_DEF_TYPE_BRIDGE) {
GList *other_config = g_list_find_custom(values, "other-config", (GCompareFunc) strcmp);
GList *external_ids = g_list_find_custom(values, "external-ids", (GCompareFunc) strcmp);
/* Non-bond/non-bridge interfaces might still be handled by the networkd backend */
if (len == 1 && (other_config || external_ids))
goto cleanup;
else if (len == 2 && other_config && external_ids)
goto cleanup;
}
/* Set the renderer for this device to NETPLAN_BACKEND_OVS, implicitly.
* But only if empty "openvswitch: {}" or "openvswitch:" with more than
* "other-config" or "external-ids" keys is given. */
npp->current.netdef->backend = NETPLAN_BACKEND_OVS;
cleanup:
g_list_free_full(values, g_free);
return ret;
}
static const mapping_entry_handler nameservers_handlers[] = {
{"search", YAML_SEQUENCE_NODE, {.generic=handle_nameservers_search}, NULL},
{"addresses", YAML_SEQUENCE_NODE, {.generic=handle_nameservers_addresses}, NULL},
{NULL}
};
/* Handlers for DHCP overrides. */
#define COMMON_DHCP_OVERRIDES_HANDLERS(overrides) \
{"hostname", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(overrides.hostname)}, \
{"route-metric", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(overrides.metric)}, \
{"send-hostname", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(overrides.send_hostname)}, \
{"use-dns", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(overrides.use_dns)}, \
{"use-domains", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(overrides.use_domains)}, \
{"use-hostname", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(overrides.use_hostname)}, \
{"use-mtu", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(overrides.use_mtu)}, \
{"use-ntp", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(overrides.use_ntp)}, \
{"use-routes", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(overrides.use_routes)}
static const mapping_entry_handler dhcp4_overrides_handlers[] = {
COMMON_DHCP_OVERRIDES_HANDLERS(dhcp4_overrides),
{NULL},
};
static const mapping_entry_handler dhcp6_overrides_handlers[] = {
COMMON_DHCP_OVERRIDES_HANDLERS(dhcp6_overrides),
{NULL},
};
/* Handlers shared by all link types */
#define COMMON_LINK_HANDLERS \
{"accept-ra", YAML_SCALAR_NODE, {.generic=handle_accept_ra}, netdef_offset(accept_ra)}, \
{"activation-mode", YAML_SCALAR_NODE, {.generic=handle_activation_mode}, netdef_offset(activation_mode)}, \
{"addresses", YAML_SEQUENCE_NODE, {.generic=handle_addresses}, NULL}, \
{"critical", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(critical)}, \
{"ignore-carrier", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(ignore_carrier)}, \
{"dhcp4", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(dhcp4)}, \
{"dhcp6", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(dhcp6)}, \
{"dhcp-identifier", YAML_SCALAR_NODE, {.generic=handle_dhcp_identifier}, NULL}, \
{"dhcp4-overrides", YAML_MAPPING_NODE, {.map={.handlers=dhcp4_overrides_handlers}}, NULL}, \
{"dhcp6-overrides", YAML_MAPPING_NODE, {.map={.handlers=dhcp6_overrides_handlers}}, NULL}, \
{"gateway4", YAML_SCALAR_NODE, {.generic=handle_gateway4}, NULL}, \
{"gateway6", YAML_SCALAR_NODE, {.generic=handle_gateway6}, NULL}, \
{"ipv6-address-generation", YAML_SCALAR_NODE, {.generic=handle_netdef_addrgen}, NULL}, \
{"ipv6-address-token", YAML_SCALAR_NODE, {.generic=handle_netdef_addrtok}, netdef_offset(ip6_addr_gen_token)}, \
{"ipv6-mtu", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(ipv6_mtubytes)}, \
{"ipv6-privacy", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(ip6_privacy)}, \
{"link-local", YAML_SEQUENCE_NODE, {.generic=handle_link_local}, NULL}, \
{"macaddress", YAML_SCALAR_NODE, {.generic=handle_netdef_mac}, netdef_offset(set_mac)}, \
{"mtu", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(mtubytes)}, \
{"nameservers", YAML_MAPPING_NODE, {.map={.handlers=nameservers_handlers}}, NULL}, \
{"optional", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(optional)}, \
{"optional-addresses", YAML_SEQUENCE_NODE, {.generic=handle_optional_addresses}, NULL}, \
{"renderer", YAML_SCALAR_NODE, {.generic=handle_netdef_renderer}, NULL}, \
{"routes", YAML_SEQUENCE_NODE, {.generic=handle_routes}, NULL}, \
{"routing-policy", YAML_SEQUENCE_NODE, {.generic=handle_ip_rules}, NULL}, \
{"neigh-suppress", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(bridge_neigh_suppress)}
#define COMMON_BACKEND_HANDLERS \
{"networkmanager", YAML_MAPPING_NODE, {.map={.handlers=nm_backend_settings_handlers}}, NULL}, \
{"openvswitch", YAML_MAPPING_NODE, {.map={.custom=handle_ovs_backend}}, NULL}
/* Handlers for physical links */
#define PHYSICAL_LINK_HANDLERS \
{"match", YAML_MAPPING_NODE, {.map={.custom=handle_match}}, NULL}, \
{"set-name", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(set_name)}, \
{"wakeonlan", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(wake_on_lan)}, \
{"wakeonwlan", YAML_SEQUENCE_NODE, {.generic=handle_wowlan}, netdef_offset(wowlan)}, \
{"emit-lldp", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(emit_lldp)}, \
{"receive-checksum-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(receive_checksum_offload)}, \
{"transmit-checksum-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(transmit_checksum_offload)}, \
{"tcp-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(tcp_segmentation_offload)}, \
{"tcp6-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(tcp6_segmentation_offload)}, \
{"generic-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(generic_segmentation_offload)}, \
{"generic-receive-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(generic_receive_offload)}, \
{"large-receive-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(large_receive_offload)}
static const mapping_entry_handler ethernet_def_handlers[] = {
COMMON_LINK_HANDLERS,
COMMON_BACKEND_HANDLERS,
PHYSICAL_LINK_HANDLERS,
{"auth", YAML_MAPPING_NODE, {.map={.custom=handle_auth}}, NULL},
{"link", YAML_SCALAR_NODE, {.generic=handle_netdef_id_ref}, netdef_offset(sriov_link)},
{"virtual-function-count", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(sriov_explicit_vf_count)},
{"embedded-switch-mode", YAML_SCALAR_NODE, {.generic=handle_embedded_switch_mode}, netdef_offset(embedded_switch_mode)},
{"delay-virtual-functions-rebind", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(sriov_delay_virtual_functions_rebind)},
{"infiniband-mode", YAML_SCALAR_NODE, {.generic=handle_ib_mode}, netdef_offset(ib_mode)},
{NULL}
};
static const mapping_entry_handler veth_def_handlers[] = {
COMMON_LINK_HANDLERS,
COMMON_BACKEND_HANDLERS,
{"peer", YAML_SCALAR_NODE, {.generic=handle_veth_peer}, netdef_offset(veth_peer_link)},
{NULL}
};
static const mapping_entry_handler wifi_def_handlers[] = {
COMMON_LINK_HANDLERS,
COMMON_BACKEND_HANDLERS,
PHYSICAL_LINK_HANDLERS,
{"access-points", YAML_MAPPING_NODE, {.map={.custom=handle_wifi_access_points}}, NULL},
{"auth", YAML_MAPPING_NODE, {.map={.custom=handle_auth}}, NULL},
{"regulatory-domain", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(regulatory_domain)},
{NULL}
};
static const mapping_entry_handler bridge_def_handlers[] = {
COMMON_LINK_HANDLERS,
COMMON_BACKEND_HANDLERS,
{"interfaces", YAML_SEQUENCE_NODE, {.generic=handle_bridge_interfaces}, NULL},
{"parameters", YAML_MAPPING_NODE, {.map={.custom=handle_bridge}}, NULL},
{NULL}
};
static const mapping_entry_handler bond_def_handlers[] = {
COMMON_LINK_HANDLERS,
COMMON_BACKEND_HANDLERS,
{"interfaces", YAML_SEQUENCE_NODE, {.generic=handle_bond_interfaces}, NULL},
{"parameters", YAML_MAPPING_NODE, {.map={.custom=handle_bonding}}, NULL},
{NULL}
};
static const mapping_entry_handler vlan_def_handlers[] = {
COMMON_LINK_HANDLERS,
COMMON_BACKEND_HANDLERS,
{"id", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(vlan_id)},
{"link", YAML_SCALAR_NODE, {.generic=handle_netdef_id_ref}, netdef_offset(vlan_link)},
{NULL}
};
static const mapping_entry_handler vrf_def_handlers[] = {
COMMON_LINK_HANDLERS,
COMMON_BACKEND_HANDLERS,
{"renderer", YAML_SCALAR_NODE, {.generic=handle_netdef_renderer}, NULL},
{"interfaces", YAML_SEQUENCE_NODE, {.generic=handle_vrf_interfaces}, NULL},
{"table", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(vrf_table)},
{"routes", YAML_SEQUENCE_NODE, {.generic=handle_routes}, NULL},
{"routing-policy", YAML_SEQUENCE_NODE, {.generic=handle_ip_rules}, NULL},
{NULL}
};
static const mapping_entry_handler modem_def_handlers[] = {
COMMON_LINK_HANDLERS,
COMMON_BACKEND_HANDLERS,
PHYSICAL_LINK_HANDLERS,
{"apn", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(modem_params.apn)},
{"auto-config", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(modem_params.auto_config)},
{"device-id", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(modem_params.device_id)},
{"network-id", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(modem_params.network_id)},
{"number", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(modem_params.number)},
{"password", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(modem_params.password)},
{"pin", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(modem_params.pin)},
{"sim-id", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(modem_params.sim_id)},
{"sim-operator-id", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(modem_params.sim_operator_id)},
{"username", YAML_SCALAR_NODE, {.generic=handle_netdef_str}, netdef_offset(modem_params.username)},
};
static const mapping_entry_handler dummy_def_handlers[] = { /* wokeignore:rule=dummy */
COMMON_LINK_HANDLERS,
COMMON_BACKEND_HANDLERS,
{NULL}
};
static const mapping_entry_handler tunnel_def_handlers[] = {
COMMON_LINK_HANDLERS,
COMMON_BACKEND_HANDLERS,
{"mode", YAML_SCALAR_NODE, {.generic=handle_tunnel_mode}, NULL},
{"local", YAML_SCALAR_NODE, {.generic=handle_tunnel_addr}, netdef_offset(tunnel.local_ip)},
{"remote", YAML_SCALAR_NODE, {.generic=handle_tunnel_addr}, netdef_offset(tunnel.remote_ip)},
{"ttl", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(tunnel_ttl)},
/* Handle key/keys for clarity in config: this can be either a scalar or
* mapping of multiple keys (input and output)
*/
{"key", YAML_NO_NODE, {.variable=handle_tunnel_key_mapping}, NULL},
{"keys", YAML_NO_NODE, {.variable=handle_tunnel_key_mapping}, NULL},
/* wireguard */
{"mark", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(tunnel.fwmark)},
{"port", YAML_SCALAR_NODE, {.generic=handle_netdef_guint}, netdef_offset(tunnel.port)},
{"peers", YAML_SEQUENCE_NODE, {.generic=handle_wireguard_peers}, NULL},
/* vxlan */
{"link", YAML_SCALAR_NODE, {.generic=handle_vxlan_id_ref}, vxlan_offset(link)},
{"ageing", YAML_SCALAR_NODE, {.generic=handle_vxlan_guint}, vxlan_offset(ageing)},
{"aging", YAML_SCALAR_NODE, {.generic=handle_vxlan_guint}, vxlan_offset(ageing)},
{"id", YAML_SCALAR_NODE, {.generic=handle_vxlan_guint}, vxlan_offset(vni)},
{"limit", YAML_SCALAR_NODE, {.generic=handle_vxlan_guint}, vxlan_offset(limit)},
{"type-of-service", YAML_SCALAR_NODE, {.generic=handle_vxlan_guint}, vxlan_offset(tos)},
{"flow-label", YAML_SCALAR_NODE, {.generic=handle_vxlan_guint}, vxlan_offset(flow_label)},
{"do-not-fragment", YAML_SCALAR_NODE, {.generic=handle_vxlan_tristate}, vxlan_offset(do_not_fragment)},
{"short-circuit", YAML_SCALAR_NODE, {.generic=handle_vxlan_tristate}, vxlan_offset(short_circuit)},
{"arp-proxy", YAML_SCALAR_NODE, {.generic=handle_vxlan_tristate}, vxlan_offset(arp_proxy)},
{"mac-learning", YAML_SCALAR_NODE, {.generic=handle_vxlan_tristate}, vxlan_offset(mac_learning)},
{"notifications", YAML_SEQUENCE_NODE, {.generic=handle_vxlan_flags}, vxlan_offset(notifications)},
{"checksums", YAML_SEQUENCE_NODE, {.generic=handle_vxlan_flags}, vxlan_offset(checksums)},
{"extensions", YAML_SEQUENCE_NODE, {.generic=handle_vxlan_flags}, vxlan_offset(extensions)},
{"port-range", YAML_SEQUENCE_NODE, {.generic=handle_vxlan_source_port}, NULL},
{NULL}
};
/****************************************************
* Grammar and handlers for network node
****************************************************/
static gboolean
handle_network_version(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
long mangled_version;
mangled_version = strtol(scalar(node), NULL, 10);
if (mangled_version < NETPLAN_VERSION_MIN || mangled_version >= NETPLAN_VERSION_MAX)
return yaml_error(npp, node, error, "Only version 2 is supported");
return TRUE;
}
static gboolean
handle_network_renderer(NetplanParser* npp, yaml_node_t* node, __unused const void* _, GError** error)
{
gboolean res = parse_renderer(npp, node, &npp->global_backend, error);
if (!npp->global_renderer)
npp->global_renderer = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
char* key = npp->current.filepath ? g_strdup(npp->current.filepath) : g_strdup("");
/* Track the global renderer value of the current file.
* If current.filepath is empty, this YAML is parsed from an unnamed YAML
* patch (e.g. via 'netplan set '). */
g_hash_table_insert(npp->global_renderer, key, GINT_TO_POINTER(npp->global_backend));
return res;
}
static gboolean
handle_network_ovs_settings_global(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const void* data, GError** error)
{
return handle_generic_map(npp, node, key_prefix, &npp->global_ovs_settings, data, error);
}
static gboolean
handle_network_ovs_settings_global_protocol(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
{
return handle_ovs_protocol(npp, node, &npp->global_ovs_settings, data, error);
}
static gboolean
handle_network_ovs_settings_global_ports(NetplanParser* npp, yaml_node_t* node, __unused const void* data, GError** error)
{
yaml_node_t* port = NULL;
yaml_node_t* peer = NULL;
yaml_node_t* pair = NULL;
yaml_node_item_t *item = NULL;
NetplanNetDefinition *component1 = NULL;
NetplanNetDefinition *component2 = NULL;
for (yaml_node_item_t *iter = node->data.sequence.items.start; iter < node->data.sequence.items.top; iter++) {
pair = yaml_document_get_node(&npp->doc, *iter);
assert_type(npp, pair, YAML_SEQUENCE_NODE);
item = pair->data.sequence.items.start;
/* A peer port definition must contain exactly 2 ports */
if (item+2 != pair->data.sequence.items.top) {
return yaml_error(npp, pair, error, "An Open vSwitch peer port sequence must have exactly two entries");
}
port = yaml_document_get_node(&npp->doc, *item);
assert_type(npp, port, YAML_SCALAR_NODE);
peer = yaml_document_get_node(&npp->doc, *(item+1));
assert_type(npp, peer, YAML_SCALAR_NODE);
if (!g_strcmp0(scalar(port), scalar(peer)))
return yaml_error(npp, peer, error, "Open vSwitch patch ports must be of different name");
/* Create port 1 netdef */
component1 = npp->parsed_defs ? g_hash_table_lookup(npp->parsed_defs, scalar(port)) : NULL;
if (!component1) {
component1 = netplan_netdef_new(npp, scalar(port), NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS);
if (g_hash_table_remove(npp->missing_id, scalar(port)))
npp->missing_ids_found++;
}
if (npp->current.filepath) {
if (component1->filepath)
g_free(component1->filepath);
component1->filepath = g_strdup(npp->current.filepath);
}
if (component1->peer && g_strcmp0(component1->peer, scalar(peer)))
return yaml_error(npp, port, error, "Open vSwitch port '%s' is already assigned to peer '%s'",
component1->id, component1->peer);
/* Create port 2 (peer) netdef */
component2 = npp->parsed_defs ? g_hash_table_lookup(npp->parsed_defs, scalar(peer)) : NULL;
if (!component2) {
component2 = netplan_netdef_new(npp, scalar(peer), NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS);
if (g_hash_table_remove(npp->missing_id, scalar(peer)))
npp->missing_ids_found++;
}
if (npp->current.filepath) {
if (component2->filepath)
g_free(component2->filepath);
component2->filepath = g_strdup(npp->current.filepath);
}
if (component2->peer && g_strcmp0(component2->peer, scalar(port)))
return yaml_error(npp, peer, error, "Open vSwitch port '%s' is already assigned to peer '%s'",
component2->id, component2->peer);
if (!component1->peer) {
component1->peer = g_strdup(scalar(peer));
component1->peer_link = component2;
}
if (!component2->peer) {
component2->peer = g_strdup(scalar(port));
component2->peer_link = component1;
}
}
return TRUE;
}
static gboolean
node_is_nulled_out(yaml_document_t* doc, yaml_node_t* node, const char* key_prefix, GHashTable* null_fields)
{
if (node->type != YAML_MAPPING_NODE)
return FALSE;
// Empty nodes are not nulled-out, they're just empty!
if (node->data.mapping.pairs.start == node->data.mapping.pairs.top)
return FALSE;
for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) {
yaml_node_t* key, *value;
g_autofree char* full_key = NULL;
key = yaml_document_get_node(doc, entry->key);
value = yaml_document_get_node(doc, entry->value);
full_key = g_strdup_printf("%s\t%s", key_prefix, key->data.scalar.value);
// null detected, so we now flip the default return.
if (g_hash_table_contains(null_fields, full_key))
continue;
if (!node_is_nulled_out(doc, value, full_key, null_fields))
return FALSE;
}
return TRUE;
}
/**
* Callback for a net device type entry like "ethernets:" in "network:"
* @data: netdef_type (as pointer)
*/
static gboolean
handle_network_type(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const void* data, GError** error)
{
for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) {
yaml_node_t* key, *value;
const mapping_entry_handler* handlers;
g_autofree char* full_key = NULL;
key = yaml_document_get_node(&npp->doc, entry->key);
if (!assert_valid_id(npp, key, error))
return FALSE;
/* globbing is not allowed for IDs */
if (strpbrk(scalar(key), "*[]?"))
return yaml_error(npp, key, error, "Definition ID '%s' must not use globbing", scalar(key));
value = yaml_document_get_node(&npp->doc, entry->value);
if (key_prefix && (npp->null_fields || npp->null_overrides)) {
full_key = g_strdup_printf("%s\t%s", key_prefix, key->data.scalar.value);
/* Ignore NULL fields (about to be deleted) */
if (npp->null_fields && (g_hash_table_contains(npp->null_fields, full_key) || node_is_nulled_out(&npp->doc, value, full_key, npp->null_fields)))
continue;
/* Ignore this netdef if it is supposed to be part of the resulting
* origin-hint file, but we're not currently processing said filepath. */
if (npp->null_overrides) {
const gchar* origin_hint = g_hash_table_lookup(npp->null_overrides, full_key);
g_autofree gchar* basename = npp->current.filepath ?
g_path_get_basename(npp->current.filepath) : NULL;
if (origin_hint && basename && g_strcmp0(origin_hint, basename) != 0)
continue;
}
}
/* special-case "renderer:" key to set the per-type backend */
if (strcmp(scalar(key), "renderer") == 0) {
if (!parse_renderer(npp, value, &npp->current.backend, error))
return FALSE;
continue;
}
assert_type(npp, value, YAML_MAPPING_NODE);
/* At this point we've seen a new starting definition, if it has been
* already mentioned in another netdef, removing it from our "missing"
* list. */
if(g_hash_table_remove(npp->missing_id, scalar(key)))
npp->missing_ids_found++;
npp->current.netdef = npp->parsed_defs ? g_hash_table_lookup(npp->parsed_defs, scalar(key)) : NULL;
if (npp->current.netdef) {
/* already exists, overriding/amending previous definition */
if (npp->current.netdef->type != GPOINTER_TO_UINT(data)) {
/* If the existing netdef is a place holder, we just repurpose it */
if (npp->current.netdef->type == NETPLAN_DEF_TYPE_NM_PLACEHOLDER_)
npp->current.netdef->type = GPOINTER_TO_UINT(data);
else
return yaml_error(npp, key, error, "Updated definition '%s' changes device type", scalar(key));
}
} else {
npp->current.netdef = netplan_netdef_new(npp, scalar(key), GPOINTER_TO_UINT(data), npp->current.backend);
}
if (npp->current.filepath) {
if (npp->current.netdef->filepath)
g_free(npp->current.netdef->filepath);
npp->current.netdef->filepath = g_strdup(npp->current.filepath);
}
// XXX: breaks multi-pass parsing.
//if (!g_hash_table_add(ids_in_file, npp->current.netdef->id))
// return yaml_error(npp, key, error, "Duplicate net definition ID '%s'", npp->current.netdef->id);
/* and fill it with definitions */
switch (npp->current.netdef->type) {
case NETPLAN_DEF_TYPE_BOND: handlers = bond_def_handlers; break;
case NETPLAN_DEF_TYPE_BRIDGE: handlers = bridge_def_handlers; break;
case NETPLAN_DEF_TYPE_ETHERNET: handlers = ethernet_def_handlers; break;
case NETPLAN_DEF_TYPE_MODEM: handlers = modem_def_handlers; break;
case NETPLAN_DEF_TYPE_TUNNEL: handlers = tunnel_def_handlers; break;
case NETPLAN_DEF_TYPE_VLAN: handlers = vlan_def_handlers; break;
case NETPLAN_DEF_TYPE_VRF: handlers = vrf_def_handlers; break;
case NETPLAN_DEF_TYPE_WIFI: handlers = wifi_def_handlers; break;
case NETPLAN_DEF_TYPE_DUMMY: handlers = dummy_def_handlers; break; /* wokeignore:rule=dummy */
case NETPLAN_DEF_TYPE_VETH: handlers = veth_def_handlers; break;
case NETPLAN_DEF_TYPE_NM:
g_debug("netplan: %s: handling NetworkManager passthrough device, settings are not fully supported.", npp->current.netdef->id);
handlers = ethernet_def_handlers;
if (npp->current.netdef->backend != NETPLAN_BACKEND_NM) {
g_warning("nm-device: %s: the renderer for nm-devices must be NetworkManager, it will be used instead of the defined one.",
npp->current.netdef->id);
npp->current.netdef->backend = NETPLAN_BACKEND_NM;
}
break;
default: g_assert_not_reached(); // LCOV_EXCL_LINE
}
/* Preprocessing */
/* Any tunnel netdef needs to carry the 'vxlan' struct, as it might
* potentially be a VXLAN tunnel. */
if (npp->current.netdef->type == NETPLAN_DEF_TYPE_TUNNEL) {
NetplanVxlan* vxlan = g_new0(NetplanVxlan, 1);
reset_vxlan(vxlan);
npp->current.vxlan = vxlan;
if (npp->current.netdef->vxlan)
g_free(npp->current.netdef->vxlan);
npp->current.netdef->vxlan = vxlan;
}
if (!process_mapping(npp, value, full_key, handlers, NULL, error))
return FALSE;
/* Postprocessing */
/* Implicit VXLAN settings, which can be deduced from parsed data. */
if (npp->current.netdef->type == NETPLAN_DEF_TYPE_TUNNEL &&
npp->current.netdef->tunnel.mode == NETPLAN_TUNNEL_MODE_VXLAN) {
if (npp->current.netdef->vxlan->link)
npp->current.netdef->vxlan->link->has_vxlans = TRUE;
else
npp->current.netdef->vxlan->independent = TRUE;
}
/* validate definition-level conditions */
if (!validate_netdef_grammar(npp, npp->current.netdef, error))
return FALSE;
/* convenience shortcut: physical device without match: means match
* name on ID */
if (npp->current.netdef->type < NETPLAN_DEF_TYPE_VIRTUAL && !npp->current.netdef->has_match)
set_str_if_null(npp->current.netdef->match.original_name, npp->current.netdef->id);
}
npp->current.backend = NETPLAN_BACKEND_NONE;
return TRUE;
}
static const mapping_entry_handler ovs_global_ssl_handlers[] = {
{"ca-cert", YAML_SCALAR_NODE, {.generic=handle_auth_str}, auth_offset(ca_certificate)},
{"certificate", YAML_SCALAR_NODE, {.generic=handle_auth_str}, auth_offset(client_certificate)},
{"private-key", YAML_SCALAR_NODE, {.generic=handle_auth_str}, auth_offset(client_key)},
{NULL}
};
static gboolean
handle_ovs_global_ssl(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, __unused const void* _, GError** error)
{
gboolean ret;
npp->current.auth = &(npp->global_ovs_settings.ssl);
ret = process_mapping(npp, node, key_prefix, ovs_global_ssl_handlers, NULL, error);
npp->current.auth = NULL;
return ret;
}
static const mapping_entry_handler ovs_network_settings_handlers[] = {
{"external-ids", YAML_MAPPING_NODE, {.map={.custom=handle_network_ovs_settings_global}}, ovs_settings_offset(external_ids)},
{"other-config", YAML_MAPPING_NODE, {.map={.custom=handle_network_ovs_settings_global}}, ovs_settings_offset(other_config)},
{"protocols", YAML_SEQUENCE_NODE, {.generic=handle_network_ovs_settings_global_protocol}, ovs_settings_offset(protocols)},
{"ports", YAML_SEQUENCE_NODE, {.generic=handle_network_ovs_settings_global_ports}, NULL},
{"ssl", YAML_MAPPING_NODE, {.map={.custom=handle_ovs_global_ssl}}, NULL},
{NULL}
};
static const mapping_entry_handler network_handlers[] = {
{"bonds", YAML_MAPPING_NODE, {.map={.custom=handle_network_type}}, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_BOND)},
{"bridges", YAML_MAPPING_NODE, {.map={.custom=handle_network_type}}, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_BRIDGE)},
{"ethernets", YAML_MAPPING_NODE, {.map={.custom=handle_network_type}}, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_ETHERNET)},
{"renderer", YAML_SCALAR_NODE, {.generic=handle_network_renderer}, NULL},
{"tunnels", YAML_MAPPING_NODE, {.map={.custom=handle_network_type}}, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_TUNNEL)},
{"version", YAML_SCALAR_NODE, {.generic=handle_network_version}, NULL},
{"vlans", YAML_MAPPING_NODE, {.map={.custom=handle_network_type}}, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_VLAN)},
{"vrfs", YAML_MAPPING_NODE, {.map={.custom=handle_network_type}}, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_VRF)},
{"wifis", YAML_MAPPING_NODE, {.map={.custom=handle_network_type}}, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_WIFI)},
{"modems", YAML_MAPPING_NODE, {.map={.custom=handle_network_type}}, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_MODEM)},
{"dummy-devices", YAML_MAPPING_NODE, {.map={.custom=handle_network_type}}, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_DUMMY)}, /* wokeignore:rule=dummy */
{"virtual-ethernets", YAML_MAPPING_NODE, {.map={.custom=handle_network_type}}, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_VETH)},
{"nm-devices", YAML_MAPPING_NODE, {.map={.custom=handle_network_type}}, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_NM)},
{"openvswitch", YAML_MAPPING_NODE, {.map={.handlers=ovs_network_settings_handlers}}, NULL},
{NULL}
};
/****************************************************
* Grammar and handlers for root node
****************************************************/
static const mapping_entry_handler root_handlers[] = {
{"network", YAML_MAPPING_NODE, {.map={.handlers=network_handlers}}, NULL},
{NULL}
};
/*
* Post-process some specific missing interfaces that are not required
* to exist but are needed in order to generate backend configuration.
*/
static void
process_missing_ids(NetplanParser* npp, __unused GError** error)
{
GHashTableIter iter;
gpointer key, value;
if (g_hash_table_size(npp->missing_id) == 0)
return;
g_hash_table_iter_init(&iter, npp->missing_id);
while (g_hash_table_iter_next(&iter, &key, &value)) {
NetplanMissingNode* missing = (NetplanMissingNode*) value;
NetplanNetDefinition* netdef = g_hash_table_lookup(npp->parsed_defs, missing->netdef_id);
NetplanBackend backend = netdef->backend != NETPLAN_BACKEND_NONE ? netdef->backend : npp->global_backend;
/* VLAN case: NetworkManager doesn't enforce the existence of a parent interface in order to
* create a VLAN.
*/
if (netdef->type == NETPLAN_DEF_TYPE_VLAN && backend == NETPLAN_BACKEND_NM) {
netdef->vlan_link = netplan_netdef_new(npp, scalar(missing->node), NETPLAN_DEF_TYPE_NM_PLACEHOLDER_, NETPLAN_BACKEND_NM);
g_hash_table_iter_remove(&iter);
}
/* VETH case: NetworkManager doesn't enforce the existence of the veth peer.
* NM will create one connection for each veth in the pair. In this case, due to our integration with
* NM (netplan-everywhere), we can't enforce the existence of both peers at a given moment because
* they might be created one after the other.
* When we find that the peer is missing, we create a temporary one using the placeholder type. That is necessary
* so we can generate the keyfile referring to the correct peer name, even though it still doesn't exist.
*/
if (netdef->type == NETPLAN_DEF_TYPE_VETH && backend == NETPLAN_BACKEND_NM) {
netdef->veth_peer_link = netplan_netdef_new(npp, scalar(missing->node), NETPLAN_DEF_TYPE_NM_PLACEHOLDER_, NETPLAN_BACKEND_NM);
g_hash_table_iter_remove(&iter);
}
}
}
/**
* Handle multiple-pass parsing of the yaml document.
*/
static gboolean
process_document(NetplanParser* npp, GError** error)
{
gboolean ret;
int previously_found;
int still_missing;
g_assert(npp->missing_id == NULL);
npp->missing_id = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
do {
g_debug("starting new processing pass");
previously_found = npp->missing_ids_found;
npp->missing_ids_found = 0;
g_clear_error(error);
ret = process_mapping(npp, yaml_document_get_root_node(&npp->doc), "", root_handlers, NULL, error);
still_missing = g_hash_table_size(npp->missing_id);
if (still_missing > 0 && npp->missing_ids_found == previously_found)
break;
} while (still_missing > 0 || npp->missing_ids_found > 0);
/* If an error already occurred we should return and not assume it's a missing interface*/
if (error && *error)
goto cleanup;
process_missing_ids(npp, error);
if (g_hash_table_size(npp->missing_id) > 0) {
GHashTableIter iter;
gpointer key, value;
NetplanMissingNode *missing;
g_clear_error(error);
/* Get the first missing identifier we can get from our list, to
* approximate early failure and give the user a meaningful error. */
g_hash_table_iter_init (&iter, npp->missing_id);
g_hash_table_iter_next (&iter, &key, &value);
missing = (NetplanMissingNode*) value;
ret = yaml_error(npp, missing->node, error, "%s: interface '%s' is not defined",
missing->netdef_id, (char*)key);
goto cleanup;
}
cleanup:
g_hash_table_destroy(npp->missing_id);
npp->missing_id = NULL;
return ret;
}
static gboolean
_netplan_parser_load_single_file(NetplanParser* npp, const char *opt_filepath, yaml_document_t *doc, GError** error)
{
int ret = FALSE;
if (opt_filepath) {
char* source = g_strdup(opt_filepath);
if (!npp->sources)
npp->sources = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
g_hash_table_add(npp->sources, source);
}
/* empty file? */
if (yaml_document_get_root_node(doc) == NULL)
return TRUE;
g_assert(npp->ids_in_file == NULL);
npp->ids_in_file = g_hash_table_new(g_str_hash, NULL);
npp->current.filepath = opt_filepath? g_strdup(opt_filepath) : NULL;
ret = process_document(npp, error);
g_free((void *)npp->current.filepath);
npp->current.filepath = NULL;
yaml_document_delete(doc);
g_hash_table_destroy(npp->ids_in_file);
npp->ids_in_file = NULL;
return ret;
}
/**
* Parse given YAML file from FD and create/update the parser's "netdefs" list.
*/
gboolean
netplan_parser_load_yaml_from_fd(NetplanParser* npp, int fd, GError** error)
{
yaml_document_t *doc = &npp->doc;
if (!load_yaml_from_fd(fd, doc, error))
return FALSE;
return _netplan_parser_load_single_file(npp, NULL, doc, error);
}
/**
* Parse given YAML file and create/update the parser's "netdefs" list.
*/
gboolean
netplan_parser_load_yaml(NetplanParser* npp, const char* filename, GError** error)
{
yaml_document_t *doc = &npp->doc;
/* Log a warning if a file can be read or written by a non-owner.
* It could contain sensitive information (e.g. WiFi passwords), so should
* stay secret. */
mode_t mask = S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
struct stat info;
if (stat(filename, &info) < 0) {
g_set_error(error, NETPLAN_FILE_ERROR, errno, "Cannot stat %s: %m", filename);
return FALSE;
} else if (info.st_mode & mask)
g_warning("Permissions for %s are too open. Netplan configuration "
"should NOT be accessible by others.", filename);
if (!load_yaml(filename, doc, error))
return FALSE;
return _netplan_parser_load_single_file(npp, filename, doc, error);
}
static gboolean
finish_iterator(const NetplanParser* npp, NetplanNetDefinition* nd, GError **error)
{
/* Take more steps to make sure we always have a backend set for netdefs */
if (nd->backend == NETPLAN_BACKEND_NONE) {
nd->backend = get_default_backend_for_type(npp->global_backend, nd->type);
g_debug("%s: setting default backend to %i", nd->id, nd->backend);
}
/* Do a final pass of validation for backend-specific conditions */
return validate_backend_rules(npp, nd, error) && validate_sriov_rules(npp, nd, error);
}
static gboolean
insert_kv_into_hash(void *key, void *value, void *hash)
{
g_hash_table_insert(hash, key, value);
return TRUE;
}
gboolean
netplan_state_import_parser_results(NetplanState* np_state, NetplanParser* npp, GError** error)
{
if (npp->parsed_defs) {
GError *recoverable = NULL;
GHashTableIter iter;
gpointer key, value;
char *regdom = NULL;
g_debug("We have some netdefs, pass them through a final round of validation");
/* Check/adopt VRF routes before route consistency and validation */
if (!adopt_and_validate_vrf_routes(npp, npp->parsed_defs, error))
return FALSE;
if (!validate_default_route_consistency(npp, npp->parsed_defs, &recoverable)) {
g_warning("Problem encountered while validating default route consistency."
"Please set up multiple routing tables and use `routing-policy` instead.\n"
"Error: %s", (recoverable) ? recoverable->message : "");
g_clear_error(&recoverable);
}
g_hash_table_iter_init (&iter, npp->parsed_defs);
while (g_hash_table_iter_next (&iter, &key, &value)) {
g_assert(np_state->netdefs == NULL ||
g_hash_table_lookup(np_state->netdefs, key) == NULL);
NetplanNetDefinition *nd = value;
if (nd->regulatory_domain) {
if (!regdom)
regdom = nd->regulatory_domain;
else if (g_strcmp0(regdom, nd->regulatory_domain) != 0)
g_warning("%s: Conflicting regulatory-domain (%s vs %s)",
nd->id, regdom, nd->regulatory_domain);
}
if (!finish_iterator(npp, nd, error))
return FALSE;
g_debug("Configuration is valid");
}
}
if (npp->parsed_defs) {
if (!np_state->netdefs)
np_state->netdefs = g_hash_table_new(g_str_hash, g_str_equal);
g_hash_table_foreach_steal(npp->parsed_defs, insert_kv_into_hash, np_state->netdefs);
}
np_state->netdefs_ordered = g_list_concat(np_state->netdefs_ordered, npp->ordered);
np_state->ovs_settings = npp->global_ovs_settings;
np_state->backend = npp->global_backend;
if (npp->sources) {
if (!np_state->sources)
np_state->sources = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
g_hash_table_foreach_steal(npp->sources, insert_kv_into_hash, np_state->sources);
}
if (npp->global_renderer) {
if (!np_state->global_renderer)
np_state->global_renderer = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
g_hash_table_foreach_steal(npp->global_renderer, insert_kv_into_hash, np_state->global_renderer);
}
/* We need to reset those fields manually as we transfered ownership of the underlying
data to out. If we don't do this, netplan_clear_parser will deallocate data
that we don't own anymore. */
npp->ordered = NULL;
memset(&npp->global_ovs_settings, 0, sizeof(NetplanOVSSettings));
netplan_parser_reset(npp);
return TRUE;
}
NetplanParser*
netplan_parser_new()
{
NetplanParser* npp = g_new0(NetplanParser, 1);
netplan_parser_reset(npp);
return npp;
}
void
netplan_parser_reset(NetplanParser* npp)
{
g_assert(npp != NULL);
if(npp->parsed_defs) {
/* FIXME: make sure that any dynamically allocated netdef data is freed */
g_hash_table_destroy(npp->parsed_defs);
npp->parsed_defs = NULL;
}
if(npp->ordered) {
g_clear_list(&npp->ordered, clear_netdef_from_list);
npp->ordered = NULL;
}
npp->global_backend = NETPLAN_BACKEND_NONE;
reset_ovs_settings(&npp->global_ovs_settings);
/* These pointers are non-owning, it's not our place to free their resources*/
npp->current.netdef = NULL;
npp->current.auth = NULL;
npp->current.vxlan = NULL;
access_point_clear(&npp->current.access_point, npp->current.backend);
wireguard_peer_clear(&npp->current.wireguard_peer);
address_options_clear(&npp->current.addr_options);
route_clear(&npp->current.route);
ip_rule_clear(&npp->current.ip_rule);
g_free((void *)npp->current.filepath);
npp->current.filepath = NULL;
// LCOV_EXCL_START
if (npp->ids_in_file) {
g_hash_table_destroy(npp->ids_in_file);
npp->ids_in_file = NULL;
}
// LCOV_EXCL_STOP
if (npp->missing_id) {
g_hash_table_destroy(npp->missing_id);
npp->missing_id = NULL;
}
npp->missing_ids_found = 0;
if (npp->null_fields) {
g_hash_table_destroy(npp->null_fields);
npp->null_fields = NULL;
}
if (npp->null_overrides) {
g_hash_table_destroy(npp->null_overrides);
npp->null_overrides = NULL;
}
if (npp->sources) {
/* Properly configured at creation not to leak */
g_hash_table_destroy(npp->sources);
npp->sources = NULL;
}
if (npp->global_renderer) {
g_hash_table_destroy(npp->global_renderer);
npp->global_renderer = NULL;
}
}
void
netplan_parser_clear(NetplanParser** npp_p)
{
NetplanParser* npp = *npp_p;
*npp_p = NULL;
netplan_parser_reset(npp);
g_free(npp);
}
/* Check if this is a Netdef-ID or global keyword which can be nullified.
* Overrides (depending on YAML hierarchy) can only happen on global values
* (like "renderer") or on the individual netdef level.
* @return the Netdef-ID/keyword or NULL */
static gboolean
is_netdef_id_or_global_value(const char* full_key)
{
g_autofree gchar* key = g_strstrip(g_strdup(full_key)); // strip leading '\t'
gboolean ret = FALSE;
gchar** split = g_strsplit(key, "\t", 0);
if (split[0] && g_strcmp0(split[0], "network") == 0) {
if (split[1]) {
if (g_strcmp0(split[1], "renderer") == 0) {
ret = TRUE; // a valid global keyword
goto cleanup;
}
/* check if is valid network type */
for (unsigned i = 0; i < NETPLAN_DEF_TYPE_MAX_; ++i) {
const char* def_type_name = netplan_def_type_name(i);
if (def_type_name && g_strcmp0(split[1], def_type_name) == 0) {
/* return keyword if split[2] is a Netdef-ID
* e.g. "network.ethernets.eth0" */
if (split[2] && !split[3]) {
ret = TRUE; // a valid Netdef-ID
break;
}
}
}
}
}
cleanup:
g_strfreev(split);
return ret;
}
static void
extract_null_fields(yaml_document_t* doc, yaml_node_t* node, GHashTable* null_fields, char* key_prefix, const char* origin_hint)
{
yaml_node_pair_t* entry;
switch (node->type) {
// LCOV_EXCL_START
case YAML_NO_NODE:
g_hash_table_insert(null_fields, key_prefix, NULL);
key_prefix = NULL;
break;
// LCOV_EXCL_STOP
case YAML_SCALAR_NODE:
if ( g_ascii_strcasecmp("null", scalar(node)) == 0
|| g_strcmp0((char*)node->tag, YAML_NULL_TAG) == 0
|| g_strcmp0(scalar(node), "~") == 0) {
g_hash_table_insert(null_fields, key_prefix, NULL);
key_prefix = NULL;
}
break;
case YAML_SEQUENCE_NODE:
/* Do nothing, we don't support nullifying *inside* sequences */
break;
case YAML_MAPPING_NODE:
for (entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) {
yaml_node_t* key, *value;
char* full_key;
key = yaml_document_get_node(doc, entry->key);
value = yaml_document_get_node(doc, entry->value);
full_key = g_strdup_printf("%s\t%s", key_prefix, key->data.scalar.value);
/* If an origin_hint is given, nullify the overrides, like
* Netdef-IDs or global values (e.g. "renderer") and track the
* origin_hint filename as hashmap value. To ignore such netdefs
* or globals during the YAML parsing stage should they be
* defined somewhere else outside the origin-hint file. */
if (origin_hint && is_netdef_id_or_global_value(full_key)) {
g_hash_table_insert(null_fields, g_strdup(full_key), g_strdup(origin_hint));
g_debug("ignoring previous definition of: %s (except in %s)", full_key, origin_hint);
}
extract_null_fields(doc, value, null_fields, full_key, origin_hint);
}
break;
// LCOV_EXCL_START
default:
g_assert(FALSE); // supposedly unreachable!
// LCOV_EXCL_STOP
}
g_free(key_prefix);
}
gboolean
netplan_parser_load_nullable_fields(NetplanParser* npp, int input_fd, GError** error)
{
yaml_document_t doc;
if (!load_yaml_from_fd(input_fd, &doc, error))
return FALSE; // LCOV_EXCL_LINE
/* empty file? */
if (yaml_document_get_root_node(&doc) == NULL)
return TRUE; // LCOV_EXCL_LINE
if (!npp->null_fields)
npp->null_fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
extract_null_fields(&doc, yaml_document_get_root_node(&doc), npp->null_fields, g_strdup(""), NULL);
yaml_document_delete(&doc);
return TRUE;
}
gboolean
netplan_parser_load_nullable_overrides(
NetplanParser* npp, int input_fd, const char* constraint, GError** error)
{
yaml_document_t doc;
if (!load_yaml_from_fd(input_fd, &doc, error))
return FALSE; // LCOV_EXCL_LINE
/* empty file? */
if (yaml_document_get_root_node(&doc) == NULL)
return TRUE; // LCOV_EXCL_LINE
if (!npp->null_overrides)
npp->null_overrides = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
/* Track the given origin_hint filename, as a constraint, for any netdef or
* global value of the given (i.e. YAML patch), so that those can
* be ignored later (inside YAML the parsing stage), shouldn't they
* originate from the origin-hint file, but from some other YAML file inside
* the hierarchy.
*
* Examples for "origin_hint:hint.yaml" being tracked in npp->null_overrides:
* yaml patch: "network.ethernets.eth0.dhcp4=false"
* => network.ethernets.eth0: hint.yaml
* yaml patch: "network.renderer=NetworkManager"
* => network.renderer: hint.yaml */
extract_null_fields(&doc, yaml_document_get_root_node(&doc), npp->null_overrides, g_strdup(""), constraint);
yaml_document_delete(&doc);
return TRUE;
}
netplan-0.107.1/src/sriov.c 0000664 0000000 0000000 00000012631 14536110317 0015422 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2020-2022 Canonical, Ltd.
* Author: Łukasz 'sil2100' Zemczak
* Author: Lukas Märdian
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#include
#include
#include
#include
#include
#include "util-internal.h"
#include "sriov.h"
static gboolean
write_sriov_rebind_systemd_unit(const GString* pfs, const char* rootdir, GError** error)
{
g_autofree gchar* id_escaped = NULL;
g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/multi-user.target.wants/netplan-sriov-rebind.service", NULL);
g_autofree char* path = g_strjoin(NULL, "/run/systemd/system/netplan-sriov-rebind.service", NULL);
gchar** split = NULL;
GString* s = g_string_new("[Unit]\n");
g_string_append(s, "Description=(Re-)bind SR-IOV Virtual Functions to their driver\n");
g_string_append_printf(s, "After=network.target\n");
/* Run after udev */
split = g_strsplit(pfs->str, " ", 0);
for (unsigned i = 0; split[i]; ++i)
g_string_append_printf(s, "After=sys-subsystem-net-devices-%s.device\n",
split[i]);
g_strfreev(split);
g_string_append(s, "\n[Service]\nType=oneshot\n");
g_string_append_printf(s, "ExecStart=" SBINDIR "/netplan rebind %s\n", pfs->str);
g_string_free_to_file(s, rootdir, path, NULL);
safe_mkdir_p_dir(link);
if (symlink(path, link) < 0 && errno != EEXIST) {
// LCOV_EXCL_START
g_set_error(error, NETPLAN_FILE_ERROR, errno,
"failed to create enablement symlink: %m\n");
return FALSE;
// LCOV_EXCL_STOP
}
return TRUE;
}
/**
* Finalize the SR-IOV configuration (global config)
*/
gboolean
netplan_state_finish_sriov_write(const NetplanState* np_state, const char* rootdir, __unused GError** error)
{
NetplanNetDefinition* def = NULL;
NetplanNetDefinition* pf = NULL;
gboolean any_sriov = FALSE;
gboolean ret = TRUE;
if (np_state) {
GString* pfs = g_string_new(NULL);
/* Find netdev interface names for SR-IOV PFs*/
for (GList* iterator = np_state->netdefs_ordered; iterator; iterator = iterator->next) {
def = (NetplanNetDefinition*) iterator->data;
pf = NULL;
if (def->sriov_explicit_vf_count < G_MAXUINT || def->sriov_link) {
any_sriov = TRUE;
if (def->sriov_explicit_vf_count < G_MAXUINT)
pf = def;
else if (def->sriov_link)
pf = def->sriov_link;
}
if (pf && pf->sriov_delay_virtual_functions_rebind) {
if (pf->set_name)
g_string_append_printf(pfs, "%s ", pf->set_name);
else if (!pf->has_match) /* netdef_id == interface name */
g_string_append_printf(pfs, "%s ", pf->id);
else
g_warning("%s: Cannot rebind SR-IOV virtual functions, unknown interface name. "
"Use 'netplan rebind ' to rebind manually or use the 'set-name' stanza.",
pf->id);
}
}
if (pfs->len > 0) {
g_string_truncate(pfs, pfs->len-1); /* cut trailing whitespace */
ret = write_sriov_rebind_systemd_unit(pfs, rootdir, NULL);
}
g_string_free(pfs, TRUE);
}
if (any_sriov) {
/* For now we execute apply --sriov-only everytime there is a new
SR-IOV device appearing, which is fine as it's relatively fast */
GString *udev_rule = g_string_new("ACTION==\"add\", SUBSYSTEM==\"net\", ATTRS{sriov_totalvfs}==\"?*\", RUN+=\"/usr/sbin/netplan apply --sriov-only\"\n");
g_string_free_to_file(udev_rule, rootdir, "run/udev/rules.d/99-sriov-netplan-setup.rules", NULL);
}
return ret;
}
gboolean
netplan_sriov_cleanup(const char* rootdir)
{
unlink_glob(rootdir, "/run/udev/rules.d/*-sriov-netplan-*.rules");
unlink_glob(rootdir, "/run/systemd/system/netplan-sriov-*.service");
return TRUE;
}
int
_netplan_state_get_vf_count_for_def(const NetplanState* np_state, const NetplanNetDefinition* netdef, GError** error)
{
GHashTableIter iter;
gpointer key, value;
guint count = 0;
g_hash_table_iter_init(&iter, np_state->netdefs);
while (g_hash_table_iter_next (&iter, &key, &value)) {
const NetplanNetDefinition* def = value;
if (def->sriov_link == netdef)
count++;
}
if (netdef->sriov_explicit_vf_count != G_MAXUINT && count > netdef->sriov_explicit_vf_count) {
g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_VALIDATION, "more VFs allocated than the explicit size declared: %d > %d", count, netdef->sriov_explicit_vf_count);
return -1;
}
return netdef->sriov_explicit_vf_count != G_MAXUINT ? netdef->sriov_explicit_vf_count : count;
}
netplan-0.107.1/src/sriov.h 0000664 0000000 0000000 00000001444 14536110317 0015427 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2020 Canonical, Ltd.
* Author: Łukasz 'sil2100' Zemczak
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#pragma once
#include "netplan.h"
NETPLAN_INTERNAL gboolean
netplan_sriov_cleanup(const char* rootdir);
netplan-0.107.1/src/types-internal.h 0000664 0000000 0000000 00000020553 14536110317 0017245 0 ustar 00root root 0000000 0000000 /*
* Copyright (C) 2021 Canonical, Ltd.
* Author: Simon Chopin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* 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 .
*/
#pragma once
#include "parse.h"
#include
#include
#include