about summary refs log tree commit diff
path: root/tools
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2024-05-05 00:07:30 +0200
committerMichael Biebl <biebl@debian.org>2024-05-05 00:07:30 +0200
commit34bb501be08aa2b313d88e67d6e0a7e0a3f9cfa6 (patch)
tree4e6220877828be4c6f261de09ec0cb2d80e32389 /tools
parentbba2e4b4de668db525cbfdfc35292e5a0b51671a (diff)
New upstream version 1.47.90 upstream/1.47.90
Diffstat (limited to 'tools')
-rwxr-xr-xtools/enums-to-docbook.pl2
-rwxr-xr-xtools/meson-dist-data.sh12
-rw-r--r--tools/nm-guest-data/.gitignore2
-rw-r--r--tools/nm-guest-data/README.md10
-rw-r--r--tools/nm-guest-data/bin-_nm-in-container-setup.sh.in13
-rw-r--r--tools/nm-guest-data/bin-nm-deploy.sh.in14
-rw-r--r--tools/nm-guest-data/bin-nm-env-prepare.sh.in163
-rw-r--r--tools/nm-guest-data/etc-bashrc-motd.my.in4
-rw-r--r--tools/nm-guest-data/etc-bashrc.my.in45
-rw-r--r--tools/nm-guest-data/etc-motd-container.in75
-rw-r--r--tools/nm-guest-data/etc-motd-vm.in63
-rw-r--r--tools/nm-guest-data/etc-rc.local.in8
-rw-r--r--tools/nm-guest-data/home-bash_history.in31
-rw-r--r--tools/nm-guest-data/home-behaverc.in2
-rw-r--r--tools/nm-guest-data/home-gdb_history.in2
-rw-r--r--tools/nm-guest-data/home-gdbinit.in2
-rw-r--r--tools/nm-guest-data/nm-90-my.conf.in19
-rw-r--r--tools/nm-guest-data/nm-95-user.conf.in0
-rw-r--r--tools/nm-guest-data/systemd-10-host-net.link.in6
-rw-r--r--tools/nm-guest-data/systemd-20-nm.override.in5
-rw-r--r--tools/nm-guest-data/systemd-dhcp-host.service.in14
-rwxr-xr-xtools/nm-in-container520
-rwxr-xr-xtools/nm-in-vm441
-rwxr-xr-xtools/test-build.sh56
-rwxr-xr-xtools/test-networkmanager-service.py70
25 files changed, 1557 insertions, 22 deletions
diff --git a/tools/enums-to-docbook.pl b/tools/enums-to-docbook.pl
index 8439eaee..d21cafa2 100755
--- a/tools/enums-to-docbook.pl
+++ b/tools/enums-to-docbook.pl
@@ -27,7 +27,7 @@ my $nm = shift @ARGV or die "Missing title";
 print <<END;
 <?xml version='1.0'?>
 <?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
-<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
 
 <refentry id="$id">
   <refmeta>
diff --git a/tools/meson-dist-data.sh b/tools/meson-dist-data.sh
index 1b5d3b03..da1e5e66 100755
--- a/tools/meson-dist-data.sh
+++ b/tools/meson-dist-data.sh
@@ -13,10 +13,6 @@ ensure_var_path() {
   fi
 }
 
-copy_from_build() {
-    cp -Tr "$MESON_BUILD_ROOT/$1" "$MESON_DIST_ROOT/$1"
-}
-
 if [ "$MESON_BUILD_ROOT" = "" ]; then
     if [ "$1" = "--build-root" ]; then
         MESON_BUILD_ROOT="$2"
@@ -25,11 +21,9 @@ fi
 
 ensure_var_path "MESON_DIST_ROOT"
 ensure_var_path "MESON_BUILD_ROOT"
-ensure_var_path "MESON_SOURCE_ROOT"
 
 ninja -C "$MESON_BUILD_ROOT" all libnm-doc NetworkManager-doc
 
-mkdir -p "$MESON_DIST_ROOT/docs/"
-copy_from_build /docs/api/
-copy_from_build /docs/libnm/
-copy_from_build /man/
+cp -Tr "$MESON_BUILD_ROOT/docs/api/html" "$MESON_DIST_ROOT/docs/api/html"
+cp -Tr "$MESON_BUILD_ROOT/docs/libnm/html" "$MESON_DIST_ROOT/docs/libnm/html"
+cp "$MESON_BUILD_ROOT/man/"*.[1-8] "$MESON_DIST_ROOT/man"
diff --git a/tools/nm-guest-data/.gitignore b/tools/nm-guest-data/.gitignore
new file mode 100644
index 00000000..f87048d7
--- /dev/null
+++ b/tools/nm-guest-data/.gitignore
@@ -0,0 +1,2 @@
+/data-*
+/containerfile
diff --git a/tools/nm-guest-data/README.md b/tools/nm-guest-data/README.md
new file mode 100644
index 00000000..2b6aac3a
--- /dev/null
+++ b/tools/nm-guest-data/README.md
@@ -0,0 +1,10 @@
+Run NetworkManager in Container for Development
+===============================================
+
+Run `nm-in-container` script for running NetworkManager in a non-root,
+privileged  podman container. Run `nm-in-vm` to do it in a vitual machine.
+This is useful for testing NetworkManager for development.
+
+Try `./nm-in-container --help` or `./nm-in-vm --help` to see options.  Inside
+the container, see the bash history for useful commands and read the hints in
+"/etc/motd".
diff --git a/tools/nm-guest-data/bin-_nm-in-container-setup.sh.in b/tools/nm-guest-data/bin-_nm-in-container-setup.sh.in
new file mode 100644
index 00000000..837daf77
--- /dev/null
+++ b/tools/nm-guest-data/bin-_nm-in-container-setup.sh.in
@@ -0,0 +1,13 @@
+#!/bin/bash -e
+
+if [ ! -d /tmp/sys2 ]; then
+    # `ip -netns t exec ...` will try to mount sysfs. But kernel rejects that in
+    # the container, unless a writable sysfs is already mounted. Due to --priviledged,
+    # we have /sys mounted rw, however, ip will first unmount /sys before trying to
+    # remount it. We thus need it mounted as rw one additional time.
+    #
+    # Let's do this setup step once, and never clean it up.
+    # https://github.com/containers/podman/issues/11887#issuecomment-938706628
+    mkdir /tmp/sys2
+    mount -t sysfs --make-private sysfs /tmp/sys2
+fi
diff --git a/tools/nm-guest-data/bin-nm-deploy.sh.in b/tools/nm-guest-data/bin-nm-deploy.sh.in
new file mode 100644
index 00000000..9b5385b1
--- /dev/null
+++ b/tools/nm-guest-data/bin-nm-deploy.sh.in
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -ex
+
+cd /NetworkManager
+
+if [ -f ./config.log ] ; then
+    make -j 5 install
+else
+    meson install -C build
+fi
+
+systemctl daemon-reload
+systemctl restart NetworkManager.service
diff --git a/tools/nm-guest-data/bin-nm-env-prepare.sh.in b/tools/nm-guest-data/bin-nm-env-prepare.sh.in
new file mode 100644
index 00000000..e26fb167
--- /dev/null
+++ b/tools/nm-guest-data/bin-nm-env-prepare.sh.in
@@ -0,0 +1,163 @@
+#!/bin/bash
+
+set -e
+
+die() {
+    printf '%s\n' "$*"
+    exit 1
+}
+
+do_cleanup() {
+    local IDX="$1"
+    local NAME_PREFIX="${2:-net}"
+    local PEER_PREFIX="${3:-d_}"
+    local NETNS_PREFIX="${4:-tt}"
+
+    logger --id "nm-env-prepare-$IDX" "cleanup start # $@"
+
+    pkill -F "/tmp/nm-dnsmasq-$PEER_PREFIX$IDX.pid" dnsmasq &>/dev/null || :
+    rm -rf "/tmp/nm-dnsmasq-$PEER_PREFIX$IDX.pid"
+
+    pkill -F "/tmp/nm-radvd-$PEER_PREFIX$IDX.pid" radvd &>/dev/null || :
+    rm -rf "/tmp/nm-radvd-$PEER_PREFIX$IDX.pid"
+
+    pkill -F "/tmp/nm-pppoe-$PEER_PREFIX$IDX.pid" pppoe-server &>/dev/null || :
+    rm -rf "/tmp/nm-pppoe-$PEER_PREFIX$IDX.pid"
+    rm -rf "/tmp/nm-pppoe-allip-$PEER_PREFIX$IDX"
+
+    rm -rf "/tmp/nm-radvd-$PEER_PREFIX$IDX.conf"
+
+    ip -netns "$NETNS_PREFIX$IDX" link del "$PEER_PREFIX$IDX" &>/dev/null || :
+
+    ip netns del "$NETNS_PREFIX$IDX" &>/dev/null || :
+
+    logger --id "nm-env-prepare-$IDX" "cleanup complete # $@"
+}
+
+do_setup() {
+    local IDX="$1"
+    local NAME_PREFIX="${2:-net}"
+    local PEER_PREFIX="${3:-d_}"
+    local NETNS_PREFIX="${4:-tt}"
+
+    logger --id "nm-env-prepare-$IDX" "setup start # $@"
+
+    ip netns add "$NETNS_PREFIX$IDX"
+    ip -netns "$NETNS_PREFIX$IDX" link set name lo up
+
+    ip -netns "$NETNS_PREFIX$IDX" link add "$NAME_PREFIX$IDX" type veth peer "$PEER_PREFIX$IDX"
+    ip -netns "$NETNS_PREFIX$IDX" link set name "$NAME_PREFIX$IDX" addr aa:0f:f1:ce:00:$(printf '%02x' $IDX)
+    ip -netns "$NETNS_PREFIX$IDX" link set name "$PEER_PREFIX$IDX" addr cc:0f:f1:ce:00:$(printf '%02x' $IDX)
+    ip -netns "$NETNS_PREFIX$IDX" link set name "$PEER_PREFIX$IDX" up
+
+    ip -netns "$NETNS_PREFIX$IDX" addr add "192.168.$((120 + IDX)).1/23" dev "$PEER_PREFIX$IDX"
+    ip -netns "$NETNS_PREFIX$IDX" addr add "192:168:$((120 + IDX))::1/64" dev "$PEER_PREFIX$IDX"
+
+    # PPPoE inside the rootless container is not actually working, because
+    # /dev/ppp is not accessible. Still start it, so that we at least can
+    # test how far it goes...
+    echo "192.168.$((120 + $IDX)).180-200" > "/tmp/nm-pppoe-allip-$PEER_PREFIX$IDX"
+    ip netns exec "$NETNS_PREFIX$IDX" \
+        pppoe-server \
+            -X "/tmp/nm-pppoe-$PEER_PREFIX$IDX.pid" \
+            -S isp \
+            -C isp \
+            -L "192.168.$((120 + IDX)).1" \
+            -p "/tmp/nm-pppoe-allip-$PEER_PREFIX$IDX" \
+            -I "$PEER_PREFIX$IDX" \
+            &
+
+    ip netns exec "$NETNS_PREFIX$IDX" \
+        dnsmasq \
+            --conf-file=/dev/null \
+            --pid-file="/tmp/nm-dnsmasq-$PEER_PREFIX$IDX.pid" \
+            --no-hosts \
+            --keep-in-foreground \
+            --bind-interfaces \
+            --log-debug \
+            --log-queries \
+            --log-dhcp \
+            --except-interface=lo \
+            --clear-on-reload \
+            --listen-address="192.168.$((120 + $IDX)).1" \
+            --listen-address="192:168:$((120 + IDX))::1" \
+            --dhcp-range="192.168.$((120 + $IDX)).100,192.168.$((120 + $IDX)).150,2m" \
+            --dhcp-range="192:168:$((120 + IDX))::1:1000,192:168:$((120 + IDX))::1:2000,64,2m" \
+            --dhcp-option=option:dns-server,8.8.8.8,8.8.4.4 \
+            --dhcp-option=option6:dns-server \
+            --no-ping \
+            &
+
+    cat <<EOF > "/tmp/nm-radvd-$PEER_PREFIX$IDX.conf"
+interface $PEER_PREFIX$IDX
+{
+        AdvSendAdvert on;
+        AdvManagedFlag on;
+        prefix 192:168:$((120 + IDX))::/64
+        {
+                AdvOnLink on;
+        };
+
+};
+EOF
+    ip netns exec "$NETNS_PREFIX$IDX" \
+        radvd \
+            --config "/tmp/nm-radvd-$PEER_PREFIX$IDX.conf" \
+            --pidfile "/tmp/nm-radvd-$PEER_PREFIX$IDX.pid" \
+            --logmethod syslog \
+            -d 5 \
+            &
+
+    ip -netns ""$NETNS_PREFIX$IDX"" link set name "$NAME_PREFIX$IDX" netns $$
+
+    logger --id "nm-env-prepare-$IDX" "setup complete: netns=$NETNS_PREFIX$IDX, iface=$NAME_PREFIX$IDX, peer=$PEER_PREFIX$IDX # $@"
+}
+
+do_redo() {
+    do_cleanup "$@"
+    do_setup "$@"
+}
+
+###############################################################################
+
+IDX=1
+NAME_PREFIX=net
+PEER_PREFIX=
+CMD=redo
+for (( i=1 ; i<="$#" ; )) ; do
+    c="${@:$i:1}"
+    i=$((i+1))
+    case "$c" in
+        redo|setup|cleanup)
+            CMD="$c"
+            ;;
+        --prefix|-p)
+            NAME_PREFIX="${@:$i:1}"
+            test -n "$NAME_PREFIX" || die "missing argument to --prefix"
+            i=$((i+1))
+            ;;
+        --peer-prefix)
+            PEER_PREFIX="${@:$i:1}"
+            test -n "$PEER_PREFIX" || die "missing argument to --peer-prefix"
+            i=$((i+1))
+            ;;
+        --idx|-i)
+            test $i -le "$#" || die "missing argument to --idx"
+            IDX="${@:$i:1}"
+            i=$((i+1))
+            ;;
+        *)
+            die "invalid argument"
+            ;;
+    esac
+done
+
+if [ -z "$PEER_PREFIX" ]; then
+    if [ "$NAME_PREFIX" = net ]; then
+        PEER_PREFIX=d_
+    else
+        PEER_PREFIX="d_${NAME_PREFIX}_"
+    fi
+fi
+
+do_$CMD "$IDX" "$NAME_PREFIX" "$PEER_PREFIX"
diff --git a/tools/nm-guest-data/etc-bashrc-motd.my.in b/tools/nm-guest-data/etc-bashrc-motd.my.in
new file mode 100644
index 00000000..510d30f8
--- /dev/null
+++ b/tools/nm-guest-data/etc-bashrc-motd.my.in
@@ -0,0 +1,4 @@
+if test "$SHOW_MOTD" != 0; then
+  cat /etc/motd
+  export SHOW_MOTD=0
+fi
diff --git a/tools/nm-guest-data/etc-bashrc.my.in b/tools/nm-guest-data/etc-bashrc.my.in
new file mode 100644
index 00000000..88f2f9aa
--- /dev/null
+++ b/tools/nm-guest-data/etc-bashrc.my.in
@@ -0,0 +1,45 @@
+alias m="make -j 8"
+alias n="ninja -C build"
+
+alias l='ls -l --color=auto'
+
+ulimit -c unlimited
+
+export G_DEBUG=fatal-warnings
+
+unset DEBUGINFOD_URLS
+
+export NMCI_DEBUG=1
+
+export TERM=xterm
+
+Clean() {
+    systemctl stop NetworkManager
+    rm -i -rf /run/NetworkManager
+    nm-env-prepare.sh
+}
+
+Cat-Timestamp() {
+    while IFS=$'\n' read line; do
+        printf "[%s]: %s\n" "1689938340.403232924" "$line"
+    done
+}
+
+Journald-clear() {
+    rm -rf /var/log/journal/????????????????????????????????/*
+    systemctl restart systemd-journald
+}
+
+nm_run_gdb() {
+    systemctl stop NetworkManager.service
+    gdb --args "${1:-/opt/test/sbin/NetworkManager}" --debug
+}
+
+nm_run_normal() {
+    systemctl stop NetworkManager.service
+    "${1:-/opt/test/sbin/NetworkManager}" --debug 2>&1 | tee /tmp/nm-log.txt
+}
+
+. /usr/share/git-core/contrib/completion/git-prompt.sh
+PS1="\[\033[01;36m\]\u@\h\[\033[00m\]:\t:\[\033[01;34m\]\w\$(__git_ps1 \" \[\033[01;36m\](%s)\[\033[00m\]\")\[\033[00m\]$ "
+export GIT_PS1_SHOWDIRTYSTATE=1
diff --git a/tools/nm-guest-data/etc-motd-container.in b/tools/nm-guest-data/etc-motd-container.in
new file mode 100644
index 00000000..131f815c
--- /dev/null
+++ b/tools/nm-guest-data/etc-motd-container.in
@@ -0,0 +1,75 @@
+*** nm-in-container:
+
+find NetworkManager bind mounted at {{BASEDIR_NM}}
+run `nm-env-prepare.sh setup --idx 1` to setup test interfaces
+
+Coredumps: coredumps are not namespaced, so by default they will
+be sent to coredumpctl of the outer host, which has no idea where
+to get the debugging symbols from. A possible workaround is setting
+
+  $ echo '/tmp/core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern
+
+so that core dumps get written to file. Afterwards, restore with
+
+  $ echo '|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h' | sudo tee /proc/sys/kernel/core_pattern
+
+from /usr/lib/sysctl.d/50-coredump.conf.
+
+For example, configure NetworkManager with
+  $ ./configure \
+           --enable-address-sanitizer=no \
+           --enable-compile-warnings=yes \
+           --enable-concheck \
+           --enable-config-plugin-ibft=yes \
+           --enable-gtk-doc \
+           --enable-ifcfg-rh=yes \
+           --enable-ifcfg-suse \
+           --enable-ifnet \
+           --enable-ifupdown=yes \
+           --enable-introspection \
+           --enable-json-validation=yes \
+           --enable-maintainer-mode \
+           --enable-more-logging \
+           --enable-more-warnings=error \
+           --enable-ovs=yes \
+           --enable-polkit=yes \
+           --enable-teamdctl=yes \
+           --enable-undefined-sanitizer=no \
+           --enable-vala=yes \
+           --enable-wimax \
+           --localstatedir=/var \
+           --prefix=/opt/test \
+           --sysconfdir=/etc \
+           --with-config-dhcp-default=internal \
+           --with-config-dns-rc-manager-default=auto \
+           --with-consolekit=yes \
+           --with-consolekit=yes \
+           --with-crypto=nss \
+           --with-dhclient=yes \
+           --with-dhcpcanon=yes \
+           --with-dhcpcd=yes \
+           --with-iwd=yes \
+           --with-libnm-glib=yes \
+           --with-modem-manager-1 \
+           --with-netconfig=/bin/nowhere/netconfig \
+           --with-nm-cloud-setup=yes \
+           --with-nmcli=yes \
+           --with-nmtui=yes \
+           --with-ofono=yes \
+           --with-resolvconf=/bin/nowhere/resolvconf \
+           --with-session-tracking=systemd \
+           --with-suspend-resume=systemd \
+           --with-systemd-logind=yes \
+           --with-valgrind=yes \
+           --enable-tests="${NM_BUILD_TESTS:-yes}" \
+           --with-more-asserts="${NM_BUILD_MORE_ASSERTS:-1000}" \
+           "${NM_CONFIGURE_OTPS[@]}"
+Test with:
+  $ systemctl stop NetworkManager; /opt/test/sbin/NetworkManager --debug 2>&1 | tee -a /tmp/nm-log.txt
+
+Or better, configure with `contrib/fedora/rpm/configure-for-system.sh`,
+subsequent `make && make install` will overwrite your system's NetworkManager,
+and you can test it with `systemctl daemon-reload ; systemctl restart NetworkManager`.
+
+Run NM-ci tests after creating eth1 with
+`nm-env-prepare.sh --prefix eth -i 1 && sleep 1 && nmcli device connect eth1`.
diff --git a/tools/nm-guest-data/etc-motd-vm.in b/tools/nm-guest-data/etc-motd-vm.in
new file mode 100644
index 00000000..10ae1f9f
--- /dev/null
+++ b/tools/nm-guest-data/etc-motd-vm.in
@@ -0,0 +1,63 @@
+*** nm-in-vm:
+
+find NetworkManager bind mounted at {{BASEDIR_NM}}
+run `nm-env-prepare.sh setup --idx 1` to setup test interfaces
+
+For example, configure NetworkManager with
+  $ ./configure \
+           --enable-address-sanitizer=no \
+           --enable-compile-warnings=yes \
+           --enable-concheck \
+           --enable-config-plugin-ibft=yes \
+           --enable-gtk-doc \
+           --enable-ifcfg-rh=yes \
+           --enable-ifcfg-suse \
+           --enable-ifnet \
+           --enable-ifupdown=yes \
+           --enable-introspection \
+           --enable-json-validation=yes \
+           --enable-maintainer-mode \
+           --enable-more-logging \
+           --enable-more-warnings=error \
+           --enable-ovs=yes \
+           --enable-polkit=yes \
+           --enable-teamdctl=yes \
+           --enable-undefined-sanitizer=no \
+           --enable-vala=yes \
+           --enable-wimax \
+           --localstatedir=/var \
+           --prefix=/opt/test \
+           --sysconfdir=/etc \
+           --with-config-dhcp-default=internal \
+           --with-config-dns-rc-manager-default=auto \
+           --with-consolekit=yes \
+           --with-consolekit=yes \
+           --with-crypto=nss \
+           --with-dhclient=yes \
+           --with-dhcpcanon=yes \
+           --with-dhcpcd=yes \
+           --with-iwd=yes \
+           --with-libnm-glib=yes \
+           --with-modem-manager-1 \
+           --with-netconfig=/bin/nowhere/netconfig \
+           --with-nm-cloud-setup=yes \
+           --with-nmcli=yes \
+           --with-nmtui=yes \
+           --with-ofono=yes \
+           --with-resolvconf=/bin/nowhere/resolvconf \
+           --with-session-tracking=systemd \
+           --with-suspend-resume=systemd \
+           --with-systemd-logind=yes \
+           --with-valgrind=yes \
+           --enable-tests="${NM_BUILD_TESTS:-yes}" \
+           --with-more-asserts="${NM_BUILD_MORE_ASSERTS:-1000}" \
+           "${NM_CONFIGURE_OTPS[@]}"
+Test with:
+  $ systemctl stop NetworkManager; /opt/test/sbin/NetworkManager --debug 2>&1 | tee -a /tmp/nm-log.txt
+
+Or better, if using Fedora, configure with `contrib/fedora/rpm/configure-for-system.sh`,
+subsequent `make && make install` will overwrite your system's NetworkManager,
+and you can test it with `systemctl daemon-reload ; systemctl restart NetworkManager`.
+
+Run NM-ci tests after creating eth1 with
+`nm-env-prepare.sh --prefix eth -i 1 && sleep 1 && nmcli device connect eth1`.
diff --git a/tools/nm-guest-data/etc-rc.local.in b/tools/nm-guest-data/etc-rc.local.in
new file mode 100644
index 00000000..d59a8d76
--- /dev/null
+++ b/tools/nm-guest-data/etc-rc.local.in
@@ -0,0 +1,8 @@
+#!/bin/bash -e
+
+/usr/bin/_nm-in-container-setup.sh
+
+cat <<EOF > /etc/resolv.conf
+# Generated by /etc/rc.d/rc.local
+nameserver 8.8.8.8
+EOF
diff --git a/tools/nm-guest-data/home-bash_history.in b/tools/nm-guest-data/home-bash_history.in
new file mode 100644
index 00000000..d97c561e
--- /dev/null
+++ b/tools/nm-guest-data/home-bash_history.in
@@ -0,0 +1,31 @@
+NM-log
+NM-log /tmp/nm-log.txt
+behave -f html --stop -t ipv4_method_static_with_IP ./features/scenarios/ipv4.feature
+behave -f html --stop ./features/scenarios/vrf.feature
+cd {{BASEDIR_NM}}
+cd /NetworkManager-ci
+cd /NetworkManager
+for i in {1..9}; do nm-env-prepare.sh --prefix eth -i $i; done
+Journald-clear
+journalctl | NM-log
+journalctl --since '3 min ago' | NM-log
+m
+make
+make install
+n
+nm-deploy.sh
+nm-env-prepare.sh
+nm-env-prepare.sh --prefix eth -i 1
+nm-env-prepare.sh --prefix eth -i 1 && sleep 1 && nmcli device connect eth1
+nm_run_gdb
+nm_run_normal
+gdb /usr/sbin/NetworkManager /tmp/core.NetworkManager.
+nmcli connection add type pppoe con-name ppp-net1 ifname ppp-net1 pppoe.parent net1 service isp username test password networkmanager autoconnect no
+nmcli device connect eth1
+systemctl stop NetworkManager; /opt/test/sbin/NetworkManager --debug 2>&1 | tee -a ./nm-log.txt
+systemctl stop NetworkManager; gdb -ex run --args /opt/test/sbin/NetworkManager --debug
+systemctl stop NetworkManager
+systemctl daemon-reload ; systemctl restart NetworkManager
+(cd /NetworkManager && n && n install && systemctl daemon-reload && systemctl restart NetworkManager.service)
+(cd /NetworkManager && m && m install && systemctl daemon-reload && systemctl restart NetworkManager.service)
+systemctl status NetworkManager
diff --git a/tools/nm-guest-data/home-behaverc.in b/tools/nm-guest-data/home-behaverc.in
new file mode 100644
index 00000000..ebb49fa7
--- /dev/null
+++ b/tools/nm-guest-data/home-behaverc.in
@@ -0,0 +1,2 @@
+[behave.formatters]
+html = behave_html_formatter:HTMLFormatter
diff --git a/tools/nm-guest-data/home-gdb_history.in b/tools/nm-guest-data/home-gdb_history.in
new file mode 100644
index 00000000..39fb9abf
--- /dev/null
+++ b/tools/nm-guest-data/home-gdb_history.in
@@ -0,0 +1,2 @@
+run
+run --debug 2>&1 | tee /tmp/nm-log.txt
diff --git a/tools/nm-guest-data/home-gdbinit.in b/tools/nm-guest-data/home-gdbinit.in
new file mode 100644
index 00000000..f55a669a
--- /dev/null
+++ b/tools/nm-guest-data/home-gdbinit.in
@@ -0,0 +1,2 @@
+set history save
+set history filename ~/.gdb_history
diff --git a/tools/nm-guest-data/nm-90-my.conf.in b/tools/nm-guest-data/nm-90-my.conf.in
new file mode 100644
index 00000000..72aeb110
--- /dev/null
+++ b/tools/nm-guest-data/nm-90-my.conf.in
@@ -0,0 +1,19 @@
+[main]
+no-auto-default=*
+debug=RLIMIT_CORE,fatal-warnings
+
+[logging]
+level=TRACE
+domains=ALL,VPN_PLUGIN:TRACE
+
+[device-managed-0]
+match-device=interface-name:d_*,interface-name:tap*
+managed=0
+
+[device-managed-1]
+match-device=interface-name:net*,interface-name:eth*
+managed=1
+
+[device-host-net]
+match-device=interface-name:host_net
+managed=0
diff --git a/tools/nm-guest-data/nm-95-user.conf.in b/tools/nm-guest-data/nm-95-user.conf.in
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tools/nm-guest-data/nm-95-user.conf.in
diff --git a/tools/nm-guest-data/systemd-10-host-net.link.in b/tools/nm-guest-data/systemd-10-host-net.link.in
new file mode 100644
index 00000000..6425d926
--- /dev/null
+++ b/tools/nm-guest-data/systemd-10-host-net.link.in
@@ -0,0 +1,6 @@
+[Match]
+Driver=virtio_net
+
+[Link]
+NamePolicy=
+Name=host_net
diff --git a/tools/nm-guest-data/systemd-20-nm.override.in b/tools/nm-guest-data/systemd-20-nm.override.in
new file mode 100644
index 00000000..d8f251e4
--- /dev/null
+++ b/tools/nm-guest-data/systemd-20-nm.override.in
@@ -0,0 +1,5 @@
+# Overrides for NetworkManager.service with nm-in-container
+# Placed in /etc/systemd/system/NetworkManager.service.d/
+[Service]
+Environment=G_DEBUG=fatal-warnings
+Environment=NM_OBFUSCATE_PTR=0
diff --git a/tools/nm-guest-data/systemd-dhcp-host.service.in b/tools/nm-guest-data/systemd-dhcp-host.service.in
new file mode 100644
index 00000000..844e766c
--- /dev/null
+++ b/tools/nm-guest-data/systemd-dhcp-host.service.in
@@ -0,0 +1,14 @@
+[Unit]
+Description=dhclient service to get connectivity with the host
+After=network.target
+
+[Service]
+Type=simple
+Restart=on-failure
+User=root
+RestartSec=5
+ExecStart=dhclient -d host_net
+ExecStop=dhclient -r host_net
+
+[Install]
+WantedBy=multi-user.target
diff --git a/tools/nm-in-container b/tools/nm-in-container
new file mode 100755
index 00000000..7ff073d0
--- /dev/null
+++ b/tools/nm-in-container
@@ -0,0 +1,520 @@
+#!/bin/bash
+
+set -e
+
+###############################################################################
+# Script to create a podman container for testing NetworkManager.
+#
+# Commands:
+#  - build: build a new image, named "$CONTAINER_NAME_REPOSITORY:$CONTAINER_NAME_TAG" ("nm:nm").
+#  - run: start the container and tag it "$CONTAINER_NAME_NAME" ("nm").
+#  - exec: run bash inside the container (this is the default).
+#  - journal|j: print the journal from inside the container.
+#  - stop: stop the container.
+#  - reset: stop and delete the container.
+#  - clean: stop and delete the container and the image.
+#
+# Options:
+#  --no-cleanup: don't delete the CONTAINERFILE and other artifacts
+#  --stop: only has effect with "run". It will stop the container afterwards.
+#  -- [EXTRA_ARGS]:
+#    - with command "exec", provide a command and arguments to run in the container.
+#      Defaults to "bash".
+#    - with command "journal", additional arguments that are passed to journalctl.
+#
+# It bind mounts the current working directory inside the container.
+# You can run `make install` and run tests.
+# There is a script nm-env-prepare.sh to generate a net1 interface for testing.
+#
+# This will bind-mount the NetworkManager working tree inside the container (and symlink
+# from /NetworkManager). Create a file ".git/nm-in-container-host" to bind mount the host's
+# "/" to "/Host".
+#
+# Create a symlink ./.git/NetworkManager-ci, to also bind-mount the CI directory.
+# Create additional symlinks ./.git/nm-guest-link-*, to bind mount additional
+# directories.
+#
+# Currently NM-ci requires a working eth1.
+# Hence call `nm-env-prepare.sh --prefix eth -i 1 && sleep 1 && nmcli device connect eth1` before
+# running a CI test.
+###############################################################################
+
+if [ -z "$BASE_IMAGE" ]; then
+    if grep -q "^ID=fedora$" /etc/os-release 2>/dev/null ; then
+        BASE_IMAGE="$(sed -n 's/^VERSION_ID=\([0-9]\+\)$/fedora:\1/p' /etc/os-release)"
+    fi
+fi
+if [ -z "$BASE_IMAGE" ]; then
+    BASE_IMAGE=fedora:latest
+fi
+
+BASEDIR_NM="$(readlink -f "$(dirname "$(readlink -f "$0")")/..")"
+BASEDIR_DATA="$BASEDIR_NM/tools/nm-guest-data"
+
+SYMLINK_NAME=()
+SYMLINK_TARGET=()
+for d in $(ls -1d "$BASEDIR_NM/.git/NetworkManager-ci" "$BASEDIR_NM/.git/nm-guest-link-"* 2>/dev/null) ; do
+    NAME="${d##*/}"
+    NAME="${NAME##nm-guest-link-}"
+    TARGET="$(readlink -f "$d")"
+    test -e "$TARGET"
+    SYMLINK_NAME+=("$NAME")
+    SYMLINK_TARGET+=("$TARGET")
+done
+
+CONTAINER_NAME_REPOSITORY=${CONTAINER_NAME_REPOSITORY:-nm}
+CONTAINER_NAME_TAG=${CONTAINER_NAME_TAG:-nm}
+CONTAINER_NAME_NAME=${CONTAINER_NAME_NAME:-nm}
+
+EXEC_ENV=()
+
+###############################################################################
+
+usage() {
+    cat <<EOF
+$0: build|run|exec|stop|reset|reexec|clean|journal [--no-cleanup] [--stop] [-- EXTRA_ARGS]
+EOF
+    echo
+    awk '/^####*$/{ if(on) exit; on=1} { if (on) { if (on==2) print(substr($0,3)); on=2; } }' "$BASH_SOURCE"
+    echo
+}
+
+###############################################################################
+
+die() {
+    (
+        echo -n -e "\033[31m"
+        printf "%s" "$*"
+        echo -e "\033[0m"
+    ) >&2
+    exit 1
+}
+
+###############################################################################
+
+CLEANUP_FILES=()
+DO_CLEANUP=1
+cleanup() {
+    test "$DO_CLEANUP" = 1 || return 0
+    for f in "${CLEANUP_FILES[@]}" ; do
+        rm -rf "$f"
+    done
+}
+
+trap cleanup EXIT
+
+###############################################################################
+
+tmp_file() {
+    cat > "$1"
+    CLEANUP_FILES+=( "$1" )
+    test -z "$2" || chmod "$2" "$1"
+}
+
+gen_file() {
+    local PERMS
+    [[ $1 =~ bin-* ]] && PERMS=755 || PERMS=644
+    sed "s|{{BASEDIR_NM}}|$BASEDIR_NM|g" "$BASEDIR_DATA/$1.in" \
+        | tmp_file "$BASEDIR_DATA/data-$1" $PERMS
+}
+
+bind_files() {
+    VARIABLE_NAME="$1"
+
+    ARR=()
+    H=~
+
+    ARR+=( -v "$BASEDIR_NM:$BASEDIR_NM" )
+
+    if [ -e "$BASEDIR_NM/.git/nm-in-container-host" ] ; then
+        ARR+=( -v /:/Host )
+    fi
+
+    for i in $(seq 1 ${#SYMLINK_TARGET[@]}) ; do
+        j=$((i - 1))
+        ARR+=( -v "${SYMLINK_TARGET[$j]}:${SYMLINK_TARGET[$j]}" )
+    done
+
+    for f in ~/.gitconfig* ~/.vim* ; do
+        test -e "$f" || continue
+        f2="${f#$H/}"
+        [[ "$f2" = .viminf* ]] && continue
+        [[ "$f2" = *.tmp ]] && continue
+        [[ "$f2" = *~ ]] && continue
+        f2="/root/$f2"
+        ARR+=( -v "$f:$f2" )
+    done
+
+    eval "$VARIABLE_NAME=( \"\${ARR[@]}\" )"
+}
+
+create_dockerfile() {
+
+    local CONTAINERFILE="$1"
+    local BASE_IMAGE="$2"
+    local GEN_FILES="bin-nm-env-prepare.sh bin-nm-deploy.sh bin-_nm-in-container-setup.sh
+        etc-rc.local etc-motd-container etc-bashrc.my etc-bashrc-motd.my nm-90-my.conf
+        nm-95-user.conf home-bash_history home-gdbinit home-gdb_history home-behaverc
+        systemd-20-nm.override"
+
+    cp "$BASEDIR_NM/contrib/scripts/NM-log" "$BASEDIR_DATA/data-bin-NM-log"
+    CLEANUP_FILES+=( "$BASEDIR_DATA/data-NM-log" )
+
+    for f in $GEN_FILES; do
+        gen_file "$f"
+    done
+    chmod 755 "$BASEDIR_DATA/data-etc-rc.local"
+
+    RUN_LN_BASEDIR_NM=
+    if [ -n "$BASEDIR_NM" -a "$BASEDIR_NM" != "/NetworkManager" ] ; then
+        RUN_LN_BASEDIR_NM="RUN ln -snf \"$BASEDIR_NM\" /NetworkManager"
+    fi
+
+    RUN_LN_SYMLINK_CMDS=""
+    for i in $(seq 1 ${#SYMLINK_NAME[@]}) ; do
+        j=$((i - 1))
+        if [ -d "${SYMLINK_TARGET[$j]}" ] ; then
+            RUN_LN_SYMLINK_CMDS="$RUN_LN_SYMLINK_CMDS"$'\n'"RUN ln -snf \"${SYMLINK_TARGET[$j]}\" \"/${SYMLINK_NAME[$j]}\""
+        fi
+    done
+
+    cat <<EOF | tmp_file "$CONTAINERFILE"
+FROM $BASE_IMAGE
+
+ENTRYPOINT ["/sbin/init"]
+
+RUN sed -i 's/^tsflags=.*/tsflags=/' /etc/dnf/dnf.conf
+
+RUN dnf install -y \\
+    --skip-broken \\
+    \\
+    /usr/bin/python \\
+    /usr/sbin/ausearch \\
+    ModemManager-devel \\
+    ModemManager-glib-devel \\
+    NetworkManager \\
+    audit-libs-devel \\
+    bash-completion \\
+    bind-utils \\
+    black \\
+    bluez-libs-devel \\
+    ccache \\
+    clang \\
+    clang-tools-extra \\
+    cscope \\
+    dbus-devel \\
+    dbus-x11 \\
+    dhclient \\
+    dnsmasq \\
+    firewalld-filesystem \\
+    gcc-c++ \\
+    gdb \\
+    gettext-devel \\
+    git \\
+    glib2-doc \\
+    glibc-langpack-pl \\
+    gnutls-devel \\
+    gobject-introspection-devel \\
+    gtk-doc \\
+    intltool \\
+    iproute \\
+    iproute-tc \\
+    iptables \\
+    jansson-devel \\
+    libasan \\
+    libcurl-devel \\
+    libndp-devel \\
+    libpsl-devel \\
+    libselinux-devel \\
+    libtool \\
+    libubsan \\
+    libuuid-devel \\
+    make \\
+    meson \\
+    mlocate \\
+    mobile-broadband-provider-info-devel \\
+    newt-devel \\
+    nispor \\
+    nmstate \\
+    nss-devel \\
+    polkit-devel \\
+    ppp \\
+    ppp-devel \\
+    procps \\
+    python3-behave \\
+    python3-black \\
+    python3-dbus \\
+    python3-devel \\
+    python3-gobject \\
+    python3-pexpect \\
+    python3-pip \\
+    python3-pyte \\
+    python3-pyyaml \\
+    qt-devel \\
+    radvd \\
+    readline-devel \\
+    rp-pppoe \\
+    rpm-build \\
+    strace \\
+    systemd \\
+    systemd-devel \\
+    tcpdump \\
+    teamd-devel \\
+    vala \\
+    vala-devel \\
+    valgrind \\
+    vim \\
+    which \\
+    \\
+    'dbus*' \\
+    'openvswitch2*' \\
+    /usr/bin/pytest \\
+    /usr/bin/debuginfo-install \\
+    NetworkManager-openvpn \\
+    NetworkManager-pptp \\
+    NetworkManager-strongswan \\
+    NetworkManager-vpnc \\
+    NetworkManager-ovs \\
+    NetworkManager-wifi \\
+    NetworkManager-team \\
+    NetworkManager-ppp \\
+    cryptsetup \\
+    dhcp-client \\
+    dhcp-relay \\
+    dhcp-server \\
+    dnsmasq \\
+    dracut-network \\
+    ethtool \\
+    firewalld \\
+    gcc \\
+    git \\
+    hostapd \\
+    iproute-tc \\
+    ipsec-tools \\
+    iputils \\
+    iscsi-initiator-utils \\
+    iw \\
+    ldns \\
+    libreswan \\
+    libselinux-utils \\
+    libyaml-devel \\
+    logrotate \\
+    lvm2 \\
+    mdadm \\
+    net-tools \\
+    nfs-utils \\
+    nmap-ncat \\
+    nss-tools \\
+    openvpn \\
+    perl-IO-Pty-Easy \\
+    perl-IO-Tty \\
+    psmisc \\
+    python3-dbus \\
+    python3-gobject \\
+    python3-netaddr \\
+    qemu-kvm \\
+    radvd \\
+    rp-pppoe \\
+    scsi-target-utils \\
+    tcpdump \\
+    tcpreplay \\
+    tuned \\
+    wireguard-tools \\
+    wireshark-cli
+
+RUN dnf debuginfo-install --skip-broken NetworkManager \$(ldd /usr/sbin/NetworkManager | sed -n 's/.* => \\(.*\\) (0x[0-9A-Fa-f]*)$/\1/p' | xargs -n1 readlink -f) -y
+
+RUN dnf clean all
+
+RUN pip3 install --user behave_html_formatter || true
+
+RUN mkdir -p /etc/systemd/system/NetworkManager.service.d
+
+COPY data-bin-NM-log "/usr/bin/NM-log"
+COPY data-bin-nm-env-prepare.sh "/usr/bin/nm-env-prepare.sh"
+COPY data-bin-nm-deploy.sh "/usr/bin/nm-deploy.sh"
+COPY data-bin-_nm-in-container-setup.sh "/usr/bin/_nm-in-container-setup.sh"
+COPY data-etc-rc.local "/etc/rc.d/rc.local"
+COPY data-etc-motd-container /etc/motd
+COPY data-etc-bashrc.my /etc/bashrc.my
+COPY data-etc-bashrc-motd.my /etc/bashrc-motd.my
+COPY data-nm-90-my.conf /etc/NetworkManager/conf.d/90-my.conf
+COPY data-nm-95-user.conf /etc/NetworkManager/conf.d/95-user.conf
+COPY data-home-bash_history /root/.bash_history
+COPY data-home-gdbinit /root/.gdbinit
+COPY data-home-gdb_history /root/.gdb_history
+COPY data-home-behaverc /root/.behaverc
+COPY data-systemd-20-nm.override /etc/systemd/system/NetworkManager.service.d/20-nm.override
+
+RUN systemctl enable NetworkManager
+
+# Generate a stable machine id.
+RUN echo "10001000100010001000100010001000" > /etc/machine-id
+
+RUN echo -e "# Default from the container image\nnameserver 8.8.8.8" > /etc/resolv.conf
+
+# Generate a fixed (version 1) secret key.
+RUN mkdir -p /var/lib/NetworkManager
+RUN chmod 700 /var/lib/NetworkManager
+RUN echo -n "nm-in-container-secret-key" > /var/lib/NetworkManager/secret_key
+RUN chmod 600 /var/lib/NetworkManager/secret_key
+
+RUN sed 's/.*RateLimitBurst=.*/RateLimitBurst=0/' /etc/systemd/journald.conf -i
+
+$RUN_LN_BASEDIR_NM
+$RUN_LN_SYMLINK_CMDS
+
+RUN rm -rf /etc/NetworkManager/system-connections/*
+
+RUN echo -e '\n. /etc/bashrc.my\n' >> /etc/bashrc
+RUN echo -e '\n. /etc/bashrc-motd.my\n' >> /etc/bashrc
+
+RUN updatedb
+EOF
+}
+
+###############################################################################
+
+container_image_exists() {
+    podman image exists "$1" || return 1
+}
+
+container_exists() {
+    podman container exists "$1" || return 1
+}
+
+container_is_running() {
+    test "$(podman ps --format "{{.ID}} {{.Names}}" | sed -n "s/ $1\$/\0/p")" != "" || return 1
+}
+
+###############################################################################
+
+do_reset() {
+    podman stop "$CONTAINER_NAME_NAME" || :
+    podman rm "$CONTAINER_NAME_NAME" || :
+}
+
+do_clean() {
+    do_reset
+    podman rmi "$CONTAINER_NAME_REPOSITORY:$CONTAINER_NAME_TAG" || :
+}
+
+do_build() {
+    container_image_exists "$CONTAINER_NAME_REPOSITORY:$CONTAINER_NAME_TAG" && return 0
+
+    CONTAINERFILE="$BASEDIR_DATA/containerfile"
+    create_dockerfile "$CONTAINERFILE" "$BASE_IMAGE"
+    podman build --squash-all --tag "$CONTAINER_NAME_REPOSITORY:$CONTAINER_NAME_TAG" -f "$CONTAINERFILE"
+}
+
+do_run() {
+    do_build
+
+    if container_is_running "$CONTAINER_NAME_NAME" ; then
+        return 0
+    fi
+
+    if container_exists "$CONTAINER_NAME_NAME" ; then
+        podman start "$CONTAINER_NAME_NAME"
+        return 0
+    fi
+
+    bind_files BIND_FILES
+
+    podman run --privileged \
+        --name "$CONTAINER_NAME_NAME" \
+        --dns=none \
+        --no-hosts \
+        -d \
+        "${BIND_FILES[@]}" \
+        "$CONTAINER_NAME_REPOSITORY:$CONTAINER_NAME_TAG"
+}
+
+do_exec() {
+    do_run
+
+    local e
+    local EXTRA_ARGS=("$@")
+    if [ "${#EXTRA_ARGS[@]}" = 0 ]; then
+        EXTRA_ARGS=('bash')
+    fi
+
+    local ENV=()
+    for e in "${EXEC_ENV[@]}" ; do
+        ENV+=(-e "$e")
+    done
+
+    podman exec "${ENV[@]}" --workdir "$BASEDIR_NM" -it "$CONTAINER_NAME_NAME" "${EXTRA_ARGS[@]}"
+
+    if [ "$DO_STOP" = 1 ]; then
+        do_stop
+    fi
+}
+
+do_reexec() {
+    do_reset
+    do_exec "$@"
+}
+
+do_journal() {
+    EXEC_ENV+=( "SYSTEMD_COLORS=0" )
+    do_exec "journalctl" --no-pager "$@"
+}
+
+do_stop() {
+    container_is_running "$CONTAINER_NAME_NAME" || return 0
+    podman stop "$CONTAINER_NAME_NAME"
+}
+
+###############################################################################
+
+DO_STOP=0
+CMD=exec
+EXTRA_ARGS=()
+for (( i=1 ; i<="$#" ; )) ; do
+    c="${@:$i:1}"
+    i=$((i+1))
+    case "$c" in
+        --no-cleanup)
+            DO_CLEANUP=0
+            ;;
+        --stop)
+            DO_STOP=1
+            ;;
+        j)
+            CMD=journal
+            ;;
+        build|run|exec|stop|reset|reexec|clean|journal)
+            CMD=$c
+            ;;
+        --)
+            EXTRA_ARGS=( "${@:$i}" )
+            break
+            ;;
+        -h|--help)
+            usage
+            exit 0
+            ;;
+        *)
+            if [ "$CMD" = "journal" ]; then
+                EXTRA_ARGS=( "${@:$((i-1))}" )
+                break;
+            else
+                usage
+                die "invalid argument: $c"
+            fi
+            ;;
+    esac
+done
+
+###############################################################################
+
+test "$UID" != 0 || die "cannot run as root"
+
+if test "$CMD" != exec -a "$CMD" != journal -a "$CMD" != reexec -a "${#EXTRA_ARGS[@]}" != 0 ; then
+    die "Extra arguments are only allowed with exec|journal|reexec command"
+fi
+
+###############################################################################
+
+do_$CMD "${EXTRA_ARGS[@]}"
diff --git a/tools/nm-in-vm b/tools/nm-in-vm
new file mode 100755
index 00000000..569c766c
--- /dev/null
+++ b/tools/nm-in-vm
@@ -0,0 +1,441 @@
+#!/bin/bash
+
+set -e
+
+###############################################################################
+# Script to create a virtual machine for testing NetworkManager.
+#
+# Commands:
+#  - build: build a new VM, named "$VM" ("nm")
+#        Args: the name of the OS to build, from `virt-builder --list` (Fedora by default)
+#  - run: start the VM.
+#  - exec: run bash inside the VM, connecting via ssh (this is the default).
+#        Args: command to execute inside the VM (then, ssh session won't open).
+#  - stop: stop the VM.
+#  - reexec: stop and exec.
+#  - clean: stop the VM and delete the image.
+#
+# Options (they MUST go before the command):
+#  -v | --vm: name of the VM to run the command on (by default 'nm').
+#             This allows to have more than one VM at the same time
+#  -h | --help: show this text
+#
+# NetworkManager directories:
+#
+#   The NetworkManager root directory is mounted in the VM as a filesystem share.
+#   You can run `make install` and run tests.
+#
+#   Create a symlink ./.git/NetworkManager-ci, to share the CI directory too.
+#
+# Required packages:
+#
+#   Your host needs libvirt, libvirt-nss and guestfs-tools. To access the VM with
+#   `ssh root@$VM`, configure /etc/nsswitch.conf as explained in
+#   https://libvirt.org/nss.html (otherwise, `nm-in-vm exec` won't work, either).
+#
+# Prepare for testing:
+#
+#   There is a script nm-env-prepare.sh to generate a net1 interface for testing.
+#   Currently NM-ci requires a working eth1, so use this before running a CI test:
+#   `nm-env-prepare.sh --prefix eth -i 1 && sleep 1 && nmcli device connect eth1`
+#
+# Additional VMs:
+#
+#   By default, the VM named 'nm' is created, but additional ones can be created:
+#   $ nm-in-vm -v nm2 build
+#   $ nm-in-vm -v nm2 exec
+#   $ nm-in-vm -v nm2 stop
+#
+# Choosing a different OS:
+#
+#   By default Fedora is used, but you can choose a different OS. Most from the
+#   list `virt-builder --list` will work.
+#   $ nm-in-vm build debian-12
+#   $ nm-in-vm exec
+#   $ nm-in-vm stop
+###############################################################################
+
+# Check for libvirt
+if ! (command -v virsh && command -v virt-builder && command -v virt-install) &>/dev/null; then
+    echo "libvirt and guestfs-tools are required" >&2
+    exit 1
+fi
+
+# set defaults if user didn't define these values
+VM=${VM:="nm"}
+OS_VERSION=${OS_VERSION:=}
+RAM=${RAM:=2048}
+IMAGE_SIZE=${IMAGE_SIZE=20G}
+ROOT_PASSWORD=${ROOT_PASSWORD:=nm}
+LIBVIRT_POOL=${LIBVIRT_POOL:=default}  # only useful if BASEDIR_VM_IMAGE is empty
+BASEDIR_VM_IMAGE=${BASEDIR_VM_IMAGE:=}
+BASEDIR_NM=${BASEDIR_NM:=}
+BASEDIR_NM_CI=${BASEDIR_NM_CI:=}
+HOST_BRIDGE=${HOST_BRIDGE:=virbr0}
+SSH_LOG_LEVEL=${SSH_LOG_LEVEL:=ERROR}
+
+if [[ -z $OS_VERSION ]]; then
+    # if running Fedora, select same version, else select latest Fedora
+    if grep -q "^ID=fedora$" /etc/os-release 2>/dev/null ; then
+        OS_VERSION="$(sed -n 's/^VERSION_ID=\([0-9]\+\)$/fedora-\1/p' /etc/os-release)"
+    else
+        OS_VERSION=$(virt-builder --list | grep '^fedora' | sort | tail -n 1 | cut -d" " -f 1)
+    fi
+fi
+
+if [[ -z $BASEDIR_NM ]]; then
+    BASEDIR_NM="$(readlink -f "$(dirname -- "$BASH_SOURCE")/..")"
+fi
+
+if [[ -z $BASEDIR_NM_CI && -d "$BASEDIR_NM/.git/NetworkManager-ci" ]]; then
+    BASEDIR_NM_CI="$(readlink -f "$BASEDIR_NM/.git/NetworkManager-ci")"
+fi
+
+if [[ -z $BASEDIR_VM_IMAGE ]]; then
+    libvirt_pool_path_xml=$(virsh pool-dumpxml $LIBVIRT_POOL | grep -F '<path>')
+    if [[ $libvirt_pool_path_xml =~ \<path\>(.*)\</path\> ]]; then
+        BASEDIR_VM_IMAGE=${BASH_REMATCH[1]}
+    else
+        BASEDIR_VM_IMAGE=$BASEDIR_NM
+    fi
+fi
+
+##############################################################################
+
+do_build() {
+    local t=$'\t'
+    local ram
+    local size
+    local os_variant
+    local nm_ci_build_args
+    local nm_ci_install_args
+    local extra_pkgs
+    local install_pkgs
+    local install_files
+    local gen_files=(
+        "bin-nm-env-prepare.sh:/usr/bin/nm-env-prepare.sh"
+        "bin-nm-deploy.sh:/usr/bin/nm-deploy.sh"
+        "etc-motd-vm:/etc/motd"
+        "etc-bashrc.my:/etc/bashrc.my"
+        "nm-90-my.conf:/etc/NetworkManager/conf.d/90-my.conf"
+        "nm-95-user.conf:/etc/NetworkManager/conf.d/95-user.conf"
+        "home-bash_history:/root/.bash_history"
+        "home-gdbinit:/root/.gdbinit"
+        "home-gdb_history:/root/.gdb_history"
+        "home-behaverc:/root/.behaverc"
+        "systemd-10-host-net.link:/etc/systemd/network/10-host-net.link"
+        "systemd-dhcp-host.service:/etc/systemd/system/dhcp-host.service"
+        "systemd-20-nm.override:/etc/systemd/system/NetworkManager.service.d/20-nm.override"
+    )
+
+    (( $# > 1 )) && die "build only accepts one argument"
+    (( $# > 0 )) && OS_VERSION="$1"
+    os_variant=${OS_VERSION//-/}  # virt-install --os-variant value, deduced from OS_VERSION
+    os_variant=${os_variant/centosstream/centos-stream}
+
+    if [[ ! $VM =~ ^[a-zA-Z0-9\-]*$ ]]; then
+        echo "Invalid VM name '$VM', use only letters, numbers and '-' character"
+        return 1
+    fi
+
+    if vm_is_installed; then
+        echo "The virtual machine '$VM' is already installed, skiping build" >&2
+        return 0
+    fi
+
+    if vm_image_exists; then
+        echo "The image '$basedir_vm_image/$vm_image_file' already exists, skiping build" >&2
+        return 0
+    fi
+
+    if [[ -n $IMAGE_SIZE ]]; then
+        size=(--size "$IMAGE_SIZE")
+    fi
+
+    if [[ -n $BASEDIR_NM_CI ]]; then
+        nm_ci_build_args=(
+            --mkdir "$BASEDIR_NM_CI"
+            --link "$BASEDIR_NM_CI:/NetworkManager-ci"
+            --append-line "/etc/fstab:/NM_CI${t}$BASEDIR_NM_CI${t}9p${t}trans=virtio,rw,_netdev${t}0${t}0"
+        )
+        nm_ci_install_args=(
+            --filesystem "$BASEDIR_NM_CI,/NM_CI"
+        )
+    fi
+
+    if [[ $OS_VERSION =~ fedora || $OS_VERSION =~ centosstream ]]; then
+        extra_pkgs=(bash-completion bind-utils ccache clang-tools-extra cryptsetup cscope \'dbus\*\'
+            dhcp-client dhcp-relay dhcp-server dnsmasq dracut-network ethtool firewalld  gcc gdb
+            glibc-langpack-pl hostapd intltool iproute ipsec-tools iputils iscsi-initiator-utils
+            iw ldns libreswan libselinux-utils libyaml-devel logrotate lvm2 mdadm mlocate net-tools
+            NetworkManager NetworkManager-openvpn NetworkManager-ovs NetworkManager-ppp
+            NetworkManager-pptp NetworkManager-strongswan NetworkManager-team NetworkManager-vpnc
+            NetworkManager-wifi nfs-utils nispor nmap-ncat nmstate nss-tools openvpn
+            \'openvswitch2\*\' perl-IO-Pty-Easy perl-IO-Tty procps psmisc python3-behave
+            python3-black python3-devel python3-netaddr python3-pip python3-pyte python3-pyyaml
+            qemu-kvm radvd rp-pppoe scsi-target-utils strace systemd tcpdump tcpreplay tuned
+            /usr/bin/debuginfo-install /usr/bin/pytest /usr/bin/python vim wireguard-tools
+            wireshark-cli)
+
+        if [[ $OS_VERSION == centosstream-8 ]]; then
+            install_pkgs=(
+                --run-command "dnf -y copr enable nmstate/nm-build-deps"
+                --run-command "dnf config-manager -y --set-enabled powertools"
+                --install epel-release,epel-next-release
+            )
+        elif [[ $OS_VERSION == centosstream-9 ]]; then
+            install_pkgs=(
+                --run-command "dnf -y copr enable nmstate/nm-build-deps"
+                --run-command "dnf config-manager -y --set-enabled crb"
+                --install epel-release,epel-next-release
+            )
+        fi
+
+        install_pkgs+=(
+            --update
+            --run "$BASEDIR_NM/contrib/fedora/REQUIRED_PACKAGES"
+            --run-command "dnf install -y --skip-broken ${extra_pkgs[*]}"
+            --run-command "pip3 install --user behave_html_formatter"
+            --run-command "dnf debuginfo-install -y --skip-broken NetworkManager \
+                \$(ldd /usr/sbin/NetworkManager \
+                    | sed -n 's/.* => \(.*\) (0x[0-9A-Fa-f]*)\$/\1/p' \
+                    | xargs -n1 readlink -f)"
+        )
+    elif [[ $OS_VERSION =~ debian || $OS_VERSION =~ ubuntu ]]; then
+        extra_pkgs=(bash-completion bind9-utils ccache clang-tools cryptsetup cscope \'dbus\*\'
+            isc-dhcp-client isc-dhcp-relay isc-dhcp-server dnsmasq dracut-network ethtool firewalld
+            gcc gdb hostapd intltool iproute2 \'iputils-\*\' iw libldns3 libreswan
+            libyaml-dev logrotate lvm2 mdadm mlocate net-tools network-manager
+            network-manager-openvpn network-manager-pptp network-manager-strongswan
+            network-manager-vpnc nfs-common ncat libnss3-tools openvpn \'openvswitch2\*\' procps
+            psmisc python3-behave black python3-dev python3-netaddr python3-pip python3-pyte
+            python3-pretty-yaml qemu-kvm radvd pppoe strace systemd tcpdump tcpreplay tuned
+            debian-goodies python3-pytest python3 vim wireguard-tools tshark)
+
+        if [[ $OS_VERSION =~ debian ]]; then
+            install_pkgs=(
+                --run-command "echo deb http://deb.debian.org/debian-debug/ \$(lsb_release -cs)-debug main >> /etc/apt/sources.list.d/debug.list"
+                --run-command "echo deb http://deb.debian.org/debian-debug/ \$(lsb_release -cs)-proposed-updates-debug main >> /etc/apt/sources.list.d/debug.list"
+            )
+        elif [[ $OS_VERSION =~ ubuntu ]]; then
+            install_pkgs=(
+                --run-command "echo deb http://ddebs.ubuntu.com \$(lsb_release -cs) main restricted universe multiverse >> /etc/apt/sources.list.d/debug.list"
+                --run-command "echo deb http://ddebs.ubuntu.com \$(lsb_release -cs)-updates main restricted universe multiverse >> /etc/apt/sources.list.d/debug.list"
+                --run-command "echo deb http://ddebs.ubuntu.com \$(lsb_release -cs)-proposed main restricted universe multiverse >> /etc/apt/sources.list.d/debug.list"
+            )
+        fi
+
+        install_pkgs+=(
+            --update
+            --upload "$BASEDIR_NM/contrib/debian/REQUIRED_PACKAGES:/tmp/REQUIRED_PACKAGES"
+            --run-command "/bin/bash /tmp/REQUIRED_PACKAGES" # using only --run fails
+            --run-command "apt-get install -y \$(find-dbgsym-packages NetworkManager 2>/dev/null)"
+            --edit "/etc/locale.gen:s/^# pl_PL.UTF-8/pl_PL.UTF-8/"
+            --run-command "locale-gen"
+        )
+
+        for p in "${extra_pkgs[@]}"; do
+            install_pkgs+=(--run-command "apt-get install -y $p || :")
+        done
+    fi
+
+    install_files=(--upload "$BASEDIR_NM/contrib/scripts/NM-log:/usr/bin/NM-log")
+    for f in "${gen_files[@]}"; do
+        gen_file "${f%:*}"
+        install_files+=(--upload "$datadir/data-$f")
+    done
+
+    echo "Creating VM"
+    echo " - VM NAME: $VM"
+    echo " - OS VERSION: $OS_VERSION"
+    echo " - SIZE: $([[ -n $IMAGE_SIZE ]] && echo "$IMAGE_SIZE" || echo "don't resize")"
+    echo " - RAM: $RAM"
+    echo " - ROOT PASSWORD: $ROOT_PASSWORD"
+    echo " - IMAGE PATH: $basedir_vm_image/$vm_image_file"
+    echo " - NM DIR: $BASEDIR_NM"
+    echo " - NM CI DIR: $([[ -n $BASEDIR_NM_CI ]] && echo "$BASEDIR_NM_CI" || echo '<none>')"
+    echo " - HOST BRIDGE: $HOST_BRIDGE"
+
+    if [[ $OS_VERSION =~ centosstream ]]; then
+        echo "WARNING: NetworkManager repositories can't be shared with the guest" \
+             "(CentOS Stream doesn't support 9P filesystem). You'll need to manually" \
+             "share by NFS or make a new clone of the repository inside the guest." >&2
+    fi
+    
+    virt-builder "$OS_VERSION" \
+        --output "$basedir_vm_image/$vm_image_file" \
+        "${size[@]}" \
+        --format qcow2 \
+        --arch x86_64 \
+        --hostname "$VM" \
+        --root-password password:nm \
+        --ssh-inject root \
+        --append-line "/etc/ssh/sshd_config:PermitRootLogin yes" \
+        --run-command "ssh-keygen -A" \
+        --mkdir "$BASEDIR_NM" \
+        --link "$BASEDIR_NM:/NetworkManager" \
+        --append-line "/etc/fstab:/NM${t}$BASEDIR_NM${t}9p${t}trans=virtio,rw,_netdev${t}0${t}0" \
+        "${nm_ci_build_args[@]}" \
+        "${install_pkgs[@]}" \
+        --mkdir "/etc/systemd/system/NetworkManager.service.d" \
+        --mkdir "/etc/systemd/network" \
+        "${install_files[@]}" \
+        --write "/var/lib/NetworkManager/secret_key:nm-in-container-secret-key" \
+        --chmod "700:/var/lib/NetworkManager" \
+        --chmod "600:/var/lib/NetworkManager/secret_key" \
+        --edit "/etc/systemd/journald.conf:s/.*RateLimitBurst=.*/RateLimitBurst=0/" \
+        --delete "/etc/NetworkManager/system-connections/*" \
+        --append-line "/etc/bashrc:. /etc/bashrc.my" \
+        --run-command "updatedb" \
+        --append-line "/etc/dhcp/dhclient.conf:send host-name = gethostname();" \
+        --firstboot-command "systemctl enable --now dhcp-host"
+
+    virt-install \
+        --name "$VM" \
+        --ram "$RAM" \
+        --disk "path=$basedir_vm_image/$vm_image_file,format=qcow2" \
+        --os-variant "$os_variant" \
+        --filesystem "$BASEDIR_NM,/NM" \
+        "${nm_ci_install_args[@]}" \
+        --network "bridge=$HOST_BRIDGE" \
+        --import \
+        --autoconsole none \
+        --noreboot
+}
+
+do_clean() {
+    vm_is_running && virsh shutdown "$VM" &>/dev/null
+    vm_is_installed && virsh undefine "$VM" &>/dev/null
+    rm -f "$basedir_vm_image/$vm_image_file"
+}
+
+do_run() {
+    vm_is_installed || do_build
+    vm_is_running && return 0
+    virsh start "$VM" >&2
+}
+
+do_exec() {
+    do_run
+
+    local failed=0
+    while ! ping -c 1 "$VM" &>/dev/null; do
+        failed=$((failed + 1))
+        (( failed < 15 )) || die "Timeout trying to ping the VM"
+        sleep 1
+    done
+
+    ssh \
+        -o StrictHostKeyChecking=no \
+        -o UserKnownHostsFile=/dev/null \
+        -o LogLevel="$SSH_LOG_LEVEL" \
+        "root@$VM" "$@"
+}
+
+do_reexec() {
+    vm_is_running && do_stop
+
+    local waited=0
+    while vm_is_running; do
+        waited=$((waited + 1))
+        (( waited < 30 )) || die "Timeout waiting for VM shutdown"
+        sleep 1
+    done
+
+    do_exec "$@"
+}
+
+do_stop() {
+    vm_is_running && virsh shutdown "$VM" >&2
+}
+
+###############################################################################
+
+vm_image_exists() {
+    [[ -f "$basedir_vm_image/$vm_image_file" ]] || return 1
+}
+
+vm_is_installed() {
+    virsh list --all --name | grep --fixed-strings --line-regexp "$VM" &>/dev/null || return 1
+}
+
+vm_is_running() {
+    virsh list --name | grep --fixed-strings --line-regexp "$VM" &>/dev/null || return 1
+}
+
+gen_file() {
+    sed "s|{{BASEDIR_NM}}|$BASEDIR_NM|g" "$datadir/$1.in" > "$datadir/data-$1"
+    if [[ $1 =~ ^bin- ]]; then
+        chmod 755 "$datadir/data-$1"
+    else
+        chmod 644 "$datadir/data-$1"
+    fi
+}
+
+usage() {
+    echo "nm-in-vm [options] build|run|exec|stop|reexec|clean [command_args]"
+}
+
+help() {
+    usage
+    echo
+    awk '/^####*$/ { if (on) exit; on=-1; } !/^####*$/ { if (on) print(substr($0,3)) }' "$BASH_SOURCE"
+    echo
+}
+
+die() {
+    echo "$1" >&2
+    exit 1
+}
+
+###############################################################################
+
+cmd=
+
+while (( $# > 0 )); do
+    case "$1" in
+        build|run|exec|reexec|stop|clean)
+            cmd="$1"
+            shift
+            ;;
+        -h|--help)
+            help
+            exit 0
+            ;;
+        -v|--vm)
+            (( $# > 1 )) || die "--vm requires one argument"
+            VM="$2"
+            shift 2
+            ;;
+        --)
+            shift
+            break
+            ;;
+        *)
+            [[ $cmd != "" ]] && break
+            echo "Invalid argument '$1'" >&2
+            echo $(usage) >&2
+            exit 1
+            ;;
+    esac
+done
+
+# compute some values that depends on user selectable variables
+mkdir -p "$BASEDIR_VM_IMAGE"
+basedir_vm_image=$(readlink -f "$BASEDIR_VM_IMAGE")
+vm_image_file="$VM.qcow2"
+datadir="$BASEDIR_NM/tools/nm-guest-data"
+
+if [[ $cmd == "" ]]; then
+    cmd=exec
+fi
+
+if [[ $UID == 0 ]]; then
+    die "cannot run as root"
+fi
+
+if [[ $cmd != exec && $cmd != reexec && $cmd != build && $# != 0 ]]; then
+    die "Extra arguments are only allowed with exec|reexec|build command"
+fi
+
+do_$cmd "$@"
diff --git a/tools/test-build.sh b/tools/test-build.sh
new file mode 100755
index 00000000..04d33b6a
--- /dev/null
+++ b/tools/test-build.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+set -e
+
+die() {
+    echo "$@"
+    exit 1
+}
+
+if [[ "x$(LANG=C git clean -ndx)" != x ]]; then
+    die "The working directory is not clean. Refuse to run. Try \`git clean -dx -n\`"
+fi
+if [[ "x$(git status --porcelain)" != x ]]; then
+    die "The working directory has local changes. Refuse to run. Try \`git reset --hard\`"
+fi
+
+build_out_of_tree() {
+  local TARGET="$1"
+  local C="$2"
+  local M="$3"
+  (
+    git clean -fdx || return 1
+    NOCONFIGURE=x ./autogen.sh || return 1
+    mkdir -p x/y || return 1
+    cd x/y || return 1
+    ../../configure $C $NMTST_CONFIGURE || return 1
+     make $TARGET $M $NMTST_MAKE || return 1
+  )
+}
+
+TARGETS=("$@")
+if [ "${#TARGETS}" -lt 1 ]; then
+    TARGETS=(
+        dispatcher/nm-dispatcher
+        src/core/NetworkManager
+        src/core/dhcp/nm-dhcp-helper
+        src/core/ndisc/tests/test-ndisc-linux
+        src/core/platform/tests/monitor
+        src/nm-online/nm-online
+        src/nmcli/nmcli
+        src/nmtui/nmtui
+        $(git grep -h '\.l\?a\>' Makefile.am | sed 's/[a-zA-Z.0-9_-/]\+/\n\0\n/g' | sort -u | grep '\.l\?a$')
+    )
+fi
+
+set -x
+
+cd "$(dirname "$(readlink -f "$0")")/.."
+
+IDX=($(seq 0 $((${#TARGETS[@]} - 1))))
+IDX=($(printf '%s\n' "${IDX[@]}" | sort -R))
+for idx in "${IDX[@]}"; do
+    TARGET="${TARGETS[$idx]}"
+    echo "### $idx: TARGET=$TARGET"
+    build_out_of_tree "$TARGET" "--enable-gtk-doc" "-j 5"
+done
diff --git a/tools/test-networkmanager-service.py b/tools/test-networkmanager-service.py
index f274a460..b4268721 100755
--- a/tools/test-networkmanager-service.py
+++ b/tools/test-networkmanager-service.py
@@ -31,6 +31,37 @@ _DEFAULT_ARG = object()
 ###############################################################################
 
 
+class AliasedProperty:
+    def __init__(self, old_name, new_name):
+        self.old_name = old_name
+        self.new_name = new_name
+
+
+ALIASED_PROPERTIES = {
+    NM.SETTING_CONNECTION_SETTING_NAME: [
+        AliasedProperty(
+            NM.SETTING_CONNECTION_MASTER,
+            "controller",  # NM.SETTING_CONNECTION_CONTROLLER
+        ),
+        AliasedProperty(
+            NM.SETTING_CONNECTION_SLAVE_TYPE,
+            "port-type",  # NM.SETTING_CONNECTION_PORT_TYPE
+        ),
+        AliasedProperty(NM.SETTING_CONNECTION_AUTOCONNECT_SLAVES, "autoconnect-ports"),
+    ],
+    NM.SETTING_WIRELESS_SETTING_NAME: [
+        AliasedProperty(
+            NM.SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST, "mac-address-denylist"
+        )
+    ],
+    NM.SETTING_WIRED_SETTING_NAME: [
+        AliasedProperty(NM.SETTING_WIRED_MAC_ADDRESS_BLACKLIST, "mac-address-denylist")
+    ],
+}
+
+###############################################################################
+
+
 class Global:
     pass
 
@@ -2118,6 +2149,13 @@ class Connection(ExportedObj):
         out_signature="a{sv}",
     )
     def Update2(self, con_hash, flags, args):
+        for setting_name in ALIASED_PROPERTIES.keys():
+            if setting_name in con_hash:
+                setting = con_hash[setting_name]
+                for pty in ALIASED_PROPERTIES[setting_name]:
+                    if pty.new_name in setting and pty.old_name in setting:
+                        del setting[pty.new_name]
+
         self.update_connection(con_hash, True)
         return []
 
@@ -2329,12 +2367,16 @@ class IP4Config(ExportedObj):
                 a = {
                     "dest": Util.random_ip(seed, net="192.168.0.0/16")[0],
                     "prefix": Util.random_int(seed, 17, 32),
-                    "next-hop": None
-                    if (Util.random_int(seed) % 3 == 0)
-                    else Util.random_ip(seed, net="192.168.0.0/16")[0],
-                    "metric": -1
-                    if (Util.random_int(seed) % 3 == 0)
-                    else Util.random_int(seed, 0, 0xFFFFFFFF),
+                    "next-hop": (
+                        None
+                        if (Util.random_int(seed) % 3 == 0)
+                        else Util.random_ip(seed, net="192.168.0.0/16")[0]
+                    ),
+                    "metric": (
+                        -1
+                        if (Util.random_int(seed) % 3 == 0)
+                        else Util.random_int(seed, 0, 0xFFFFFFFF)
+                    ),
                 }
                 routes.append(a)
 
@@ -2527,12 +2569,16 @@ class IP6Config(ExportedObj):
                 a = {
                     "dest": Util.random_ip(seed, net="2001:a::/64")[0],
                     "prefix": Util.random_int(seed, 65, 128),
-                    "next-hop": None
-                    if (Util.random_int(seed) % 3 == 0)
-                    else Util.random_ip(seed, net="2001:a::/64")[0],
-                    "metric": -1
-                    if (Util.random_int(seed) % 3 == 0)
-                    else Util.random_int(seed, 0, 0xFFFFFFFF),
+                    "next-hop": (
+                        None
+                        if (Util.random_int(seed) % 3 == 0)
+                        else Util.random_ip(seed, net="2001:a::/64")[0]
+                    ),
+                    "metric": (
+                        -1
+                        if (Util.random_int(seed) % 3 == 0)
+                        else Util.random_int(seed, 0, 0xFFFFFFFF)
+                    ),
                 }
                 routes.append(a)