From 18c08bde1a40653e81b3b8e1eaa24b76f591e957 Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Thu, 4 Jul 2024 15:48:10 -0700 Subject: [PATCH 01/19] [qemu] enable and populate networks on Linux --- .../qemu/linux/qemu_platform_detail.h | 1 + .../qemu/linux/qemu_platform_detail_linux.cpp | 5 +++++ src/platform/backends/qemu/qemu_platform.h | 5 +---- .../qemu/qemu_virtual_machine_factory.cpp | 21 ++++++++++++++++++- tests/qemu/mock_qemu_platform.h | 1 + 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail.h b/src/platform/backends/qemu/linux/qemu_platform_detail.h index 0eb3501df1..4027057765 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail.h +++ b/src/platform/backends/qemu/linux/qemu_platform_detail.h @@ -41,6 +41,7 @@ class QemuPlatformDetail : public QemuPlatform void remove_resources_for(const std::string& name) override; void platform_health_check() override; QStringList vm_platform_args(const VirtualMachineDescription& vm_desc) override; + bool is_network_supported(const std::string& network_type) const override; void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) override; void prepare_networking(std::vector& extra_interfaces) const override; diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp index 5ca806e579..fb5ca52ea0 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -212,6 +212,11 @@ mp::QemuPlatform::UPtr mp::QemuPlatformFactory::make_qemu_platform(const Path& d return std::make_unique(data_dir); } +bool mp::QemuPlatformDetail::is_network_supported(const std::string& network_type) const +{ + return network_type == "bridge" || network_type == "ethernet"; +} + void mp::QemuPlatformDetail::prepare_networking(std::vector& /*extra_interfaces*/) const { // Nothing to do here until we implement networking on this backend diff --git a/src/platform/backends/qemu/qemu_platform.h b/src/platform/backends/qemu/qemu_platform.h index 8840e06fef..fc61b51579 100644 --- a/src/platform/backends/qemu/qemu_platform.h +++ b/src/platform/backends/qemu/qemu_platform.h @@ -54,10 +54,7 @@ class QemuPlatform : private DisabledCopyMove { return {}; }; - virtual std::vector networks() const - { - throw NotImplementedOnThisBackendException("networks"); - }; + virtual bool is_network_supported(const std::string& network_type) const = 0; virtual void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) = 0; virtual void prepare_networking(std::vector& extra_interfaces) const = 0; diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index dfe964c36c..9faac06a13 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -30,6 +30,7 @@ namespace mp = multipass; namespace mpl = multipass::logging; +namespace mpu = multipass::utils; namespace { @@ -129,7 +130,25 @@ QString mp::QemuVirtualMachineFactory::get_backend_directory_name() const auto mp::QemuVirtualMachineFactory::networks() const -> std::vector { - return qemu_platform->networks(); + auto platform_ifs_info = MP_PLATFORM.get_network_interfaces_info(); + + std::vector ret; + const auto& br_nomenclature = MP_PLATFORM.bridge_nomenclature(); + + for (const auto& ifs_info : platform_ifs_info) + { + const auto& info = ifs_info.second; + const auto& type = info.type; + + if (qemu_platform->is_network_supported(type)) + ret.push_back(info); + } + + for (auto& net : ret) + if (net.type == "ethernet" && net.needs_authorization && mpu::find_bridge_with(ret, net.id, br_nomenclature)) + net.needs_authorization = false; + + return ret; } void mp::QemuVirtualMachineFactory::prepare_networking(std::vector& extra_interfaces) diff --git a/tests/qemu/mock_qemu_platform.h b/tests/qemu/mock_qemu_platform.h index 28b6f3beb0..a8ec1f0059 100644 --- a/tests/qemu/mock_qemu_platform.h +++ b/tests/qemu/mock_qemu_platform.h @@ -42,6 +42,7 @@ struct MockQemuPlatform : public QemuPlatform MOCK_METHOD(QStringList, vmstate_platform_args, (), (override)); MOCK_METHOD(QStringList, vm_platform_args, (const VirtualMachineDescription&), (override)); MOCK_METHOD(QString, get_directory_name, (), (const, override)); + MOCK_METHOD(bool, is_network_supported, (const std::string&), (const, override)); MOCK_METHOD(void, add_network_interface, (VirtualMachineDescription&, const NetworkInterface&), (override)); MOCK_METHOD(void, prepare_networking, (std::vector&), (const, override)); }; From 32fef0568be5ca39c3356bc0164871a93a8e335a Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Thu, 4 Jul 2024 19:43:25 -0700 Subject: [PATCH 02/19] [qemu] wire up bridge creation for qemu on linux --- src/platform/backends/qemu/linux/qemu_platform_detail.h | 1 + .../backends/qemu/linux/qemu_platform_detail_linux.cpp | 6 ++++++ src/platform/backends/qemu/qemu_platform.h | 1 + src/platform/backends/qemu/qemu_virtual_machine_factory.cpp | 5 +++++ src/platform/backends/qemu/qemu_virtual_machine_factory.h | 1 + tests/qemu/mock_qemu_platform.h | 1 + 6 files changed, 15 insertions(+) diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail.h b/src/platform/backends/qemu/linux/qemu_platform_detail.h index 4027057765..981273d6e4 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail.h +++ b/src/platform/backends/qemu/linux/qemu_platform_detail.h @@ -44,6 +44,7 @@ class QemuPlatformDetail : public QemuPlatform bool is_network_supported(const std::string& network_type) const override; void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) override; void prepare_networking(std::vector& extra_interfaces) const override; + std::string create_bridge_with(const NetworkInterfaceInfo& interface) const override; private: const QString bridge_name; diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp index fb5ca52ea0..6cab6a260c 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -221,3 +221,9 @@ void mp::QemuPlatformDetail::prepare_networking(std::vector& / { // Nothing to do here until we implement networking on this backend } + +std::string mp::QemuPlatformDetail::create_bridge_with(const NetworkInterfaceInfo& interface) const +{ + assert(interface.type == "ethernet"); + return MP_BACKEND.create_bridge_with(interface.id); +} diff --git a/src/platform/backends/qemu/qemu_platform.h b/src/platform/backends/qemu/qemu_platform.h index fc61b51579..fac0d12abc 100644 --- a/src/platform/backends/qemu/qemu_platform.h +++ b/src/platform/backends/qemu/qemu_platform.h @@ -57,6 +57,7 @@ class QemuPlatform : private DisabledCopyMove virtual bool is_network_supported(const std::string& network_type) const = 0; virtual void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) = 0; virtual void prepare_networking(std::vector& extra_interfaces) const = 0; + virtual std::string create_bridge_with(const NetworkInterfaceInfo& interface) const = 0; protected: explicit QemuPlatform() = default; diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 9faac06a13..b7d849753a 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -155,3 +155,8 @@ void mp::QemuVirtualMachineFactory::prepare_networking(std::vectorprepare_networking(extra_interfaces); } + +std::string mp::QemuVirtualMachineFactory::create_bridge_with(const NetworkInterfaceInfo& interface) +{ + return qemu_platform->create_bridge_with(interface); +} diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.h b/src/platform/backends/qemu/qemu_virtual_machine_factory.h index 1b5221cd1b..6d9e0634bf 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.h +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.h @@ -47,6 +47,7 @@ class QemuVirtualMachineFactory final : public BaseVirtualMachineFactory protected: void remove_resources_for_impl(const std::string& name) override; + std::string create_bridge_with(const NetworkInterfaceInfo& interface) override; private: QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform, const Path& data_dir); diff --git a/tests/qemu/mock_qemu_platform.h b/tests/qemu/mock_qemu_platform.h index a8ec1f0059..3b6af1ab90 100644 --- a/tests/qemu/mock_qemu_platform.h +++ b/tests/qemu/mock_qemu_platform.h @@ -45,6 +45,7 @@ struct MockQemuPlatform : public QemuPlatform MOCK_METHOD(bool, is_network_supported, (const std::string&), (const, override)); MOCK_METHOD(void, add_network_interface, (VirtualMachineDescription&, const NetworkInterface&), (override)); MOCK_METHOD(void, prepare_networking, (std::vector&), (const, override)); + MOCK_METHOD(std::string, create_bridge_with, (const NetworkInterfaceInfo&), (const, override)); }; struct MockQemuPlatformFactory : public QemuPlatformFactory From 2d098fc2b16c3114bd51be9ba7ea5355b3ae0d7b Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Fri, 5 Jul 2024 08:56:20 -0700 Subject: [PATCH 03/19] [qemu] wire up network prep --- src/platform/backends/qemu/linux/qemu_platform_detail.h | 1 + .../backends/qemu/linux/qemu_platform_detail_linux.cpp | 5 +++++ src/platform/backends/qemu/qemu_platform.h | 1 + src/platform/backends/qemu/qemu_virtual_machine_factory.cpp | 3 ++- tests/qemu/mock_qemu_platform.h | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail.h b/src/platform/backends/qemu/linux/qemu_platform_detail.h index 981273d6e4..026113632f 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail.h +++ b/src/platform/backends/qemu/linux/qemu_platform_detail.h @@ -42,6 +42,7 @@ class QemuPlatformDetail : public QemuPlatform void platform_health_check() override; QStringList vm_platform_args(const VirtualMachineDescription& vm_desc) override; bool is_network_supported(const std::string& network_type) const override; + bool needs_network_prep() const override; void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) override; void prepare_networking(std::vector& extra_interfaces) const override; std::string create_bridge_with(const NetworkInterfaceInfo& interface) const override; diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp index 6cab6a260c..28abf06a30 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -217,6 +217,11 @@ bool mp::QemuPlatformDetail::is_network_supported(const std::string& network_typ return network_type == "bridge" || network_type == "ethernet"; } +bool mp::QemuPlatformDetail::needs_network_prep() const +{ + return true; +} + void mp::QemuPlatformDetail::prepare_networking(std::vector& /*extra_interfaces*/) const { // Nothing to do here until we implement networking on this backend diff --git a/src/platform/backends/qemu/qemu_platform.h b/src/platform/backends/qemu/qemu_platform.h index fac0d12abc..1668260ef4 100644 --- a/src/platform/backends/qemu/qemu_platform.h +++ b/src/platform/backends/qemu/qemu_platform.h @@ -55,6 +55,7 @@ class QemuPlatform : private DisabledCopyMove return {}; }; virtual bool is_network_supported(const std::string& network_type) const = 0; + virtual bool needs_network_prep() const = 0; virtual void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) = 0; virtual void prepare_networking(std::vector& extra_interfaces) const = 0; virtual std::string create_bridge_with(const NetworkInterfaceInfo& interface) const = 0; diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index b7d849753a..5cbcf34e83 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -153,7 +153,8 @@ auto mp::QemuVirtualMachineFactory::networks() const -> std::vector& extra_interfaces) { - return qemu_platform->prepare_networking(extra_interfaces); + if (qemu_platform->needs_network_prep()) + mp::BaseVirtualMachineFactory::prepare_networking(extra_interfaces); } std::string mp::QemuVirtualMachineFactory::create_bridge_with(const NetworkInterfaceInfo& interface) diff --git a/tests/qemu/mock_qemu_platform.h b/tests/qemu/mock_qemu_platform.h index 3b6af1ab90..76d23eefca 100644 --- a/tests/qemu/mock_qemu_platform.h +++ b/tests/qemu/mock_qemu_platform.h @@ -43,6 +43,7 @@ struct MockQemuPlatform : public QemuPlatform MOCK_METHOD(QStringList, vm_platform_args, (const VirtualMachineDescription&), (override)); MOCK_METHOD(QString, get_directory_name, (), (const, override)); MOCK_METHOD(bool, is_network_supported, (const std::string&), (const, override)); + MOCK_METHOD(bool, needs_network_prep, (), (const override)); MOCK_METHOD(void, add_network_interface, (VirtualMachineDescription&, const NetworkInterface&), (override)); MOCK_METHOD(void, prepare_networking, (std::vector&), (const, override)); MOCK_METHOD(std::string, create_bridge_with, (const NetworkInterfaceInfo&), (const, override)); From 7601590392f109b3e771556fdbe7ccd56510fab5 Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Fri, 5 Jul 2024 08:58:27 -0700 Subject: [PATCH 04/19] [qemu platform] remove unused function --- src/platform/backends/qemu/linux/qemu_platform_detail.h | 1 - .../backends/qemu/linux/qemu_platform_detail_linux.cpp | 5 ----- src/platform/backends/qemu/qemu_platform.h | 1 - tests/qemu/mock_qemu_platform.h | 1 - 4 files changed, 8 deletions(-) diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail.h b/src/platform/backends/qemu/linux/qemu_platform_detail.h index 026113632f..db67faf9c4 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail.h +++ b/src/platform/backends/qemu/linux/qemu_platform_detail.h @@ -44,7 +44,6 @@ class QemuPlatformDetail : public QemuPlatform bool is_network_supported(const std::string& network_type) const override; bool needs_network_prep() const override; void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) override; - void prepare_networking(std::vector& extra_interfaces) const override; std::string create_bridge_with(const NetworkInterfaceInfo& interface) const override; private: diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp index 28abf06a30..332ab5eab4 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -222,11 +222,6 @@ bool mp::QemuPlatformDetail::needs_network_prep() const return true; } -void mp::QemuPlatformDetail::prepare_networking(std::vector& /*extra_interfaces*/) const -{ - // Nothing to do here until we implement networking on this backend -} - std::string mp::QemuPlatformDetail::create_bridge_with(const NetworkInterfaceInfo& interface) const { assert(interface.type == "ethernet"); diff --git a/src/platform/backends/qemu/qemu_platform.h b/src/platform/backends/qemu/qemu_platform.h index 1668260ef4..0e7f4a21dd 100644 --- a/src/platform/backends/qemu/qemu_platform.h +++ b/src/platform/backends/qemu/qemu_platform.h @@ -57,7 +57,6 @@ class QemuPlatform : private DisabledCopyMove virtual bool is_network_supported(const std::string& network_type) const = 0; virtual bool needs_network_prep() const = 0; virtual void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) = 0; - virtual void prepare_networking(std::vector& extra_interfaces) const = 0; virtual std::string create_bridge_with(const NetworkInterfaceInfo& interface) const = 0; protected: diff --git a/tests/qemu/mock_qemu_platform.h b/tests/qemu/mock_qemu_platform.h index 76d23eefca..08130a6a7a 100644 --- a/tests/qemu/mock_qemu_platform.h +++ b/tests/qemu/mock_qemu_platform.h @@ -45,7 +45,6 @@ struct MockQemuPlatform : public QemuPlatform MOCK_METHOD(bool, is_network_supported, (const std::string&), (const, override)); MOCK_METHOD(bool, needs_network_prep, (), (const override)); MOCK_METHOD(void, add_network_interface, (VirtualMachineDescription&, const NetworkInterface&), (override)); - MOCK_METHOD(void, prepare_networking, (std::vector&), (const, override)); MOCK_METHOD(std::string, create_bridge_with, (const NetworkInterfaceInfo&), (const, override)); }; From 75ca9091a480d4dada739e8444b52e06aebf3b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Pe=C3=B1aranda?= Date: Wed, 10 Apr 2024 11:57:11 -0300 Subject: [PATCH 05/19] [qemu][linux] Add our own bridge helper. It is a stripped-down version of the QEMU bridge helper. --- .../backends/qemu/linux/bridge_helper.c | 331 ++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 src/platform/backends/qemu/linux/bridge_helper.c diff --git a/src/platform/backends/qemu/linux/bridge_helper.c b/src/platform/backends/qemu/linux/bridge_helper.c new file mode 100644 index 0000000000..02bddd0a97 --- /dev/null +++ b/src/platform/backends/qemu/linux/bridge_helper.c @@ -0,0 +1,331 @@ +/* + * QEMU Bridge Helper + * + * Copyright IBM, Corp. 2011 + * Copyright (C) Canonical, Ltd. + * + * Authors: + * Anthony Liguori + * Richa Marwaha + * Corey Bryant + * Luis PeƱaranda + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +/* This version of the bridge helper was adapted for use with Multipass. + The changes are: + - the authorization via ACL was removed; + - dependencies on other QEMU functions were replaced by common includes; + - functionality was wrapped inside a function. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#ifndef SIOCBRADDIF +#include +#endif + +#ifdef CONFIG_LIBCAP_NG +#include +#endif + +static void usage(void) +{ + fprintf(stderr, "Usage: bridge_helper [--use-vnet] --br=bridge --fd=unixfd\n"); +} + +static bool has_vnet_hdr(int fd) +{ + unsigned int features = 0; + + if (ioctl(fd, TUNGETFEATURES, &features) == -1) + { + return false; + } + + if (!(features & IFF_VNET_HDR)) + { + return false; + } + + return true; +} + +static void prep_ifreq(struct ifreq* ifr, const char* ifname) +{ + memset(ifr, 0, sizeof(*ifr)); + snprintf(ifr->ifr_name, IFNAMSIZ, "%s", ifname); +} + +static int send_fd(int c, int fd) +{ + char msgbuf[CMSG_SPACE(sizeof(fd))]; + struct msghdr msg = { + .msg_control = msgbuf, + .msg_controllen = sizeof(msgbuf), + }; + struct cmsghdr* cmsg; + struct iovec iov; + char req[1] = {0x00}; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + msg.msg_controllen = cmsg->cmsg_len; + + iov.iov_base = req; + iov.iov_len = sizeof(req); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); + + return sendmsg(c, &msg, 0); +} + +#ifdef CONFIG_LIBCAP_NG +static int drop_privileges(void) +{ + /* clear all capabilities */ + capng_clear(CAPNG_SELECT_BOTH); + + if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, CAP_NET_ADMIN) < 0) + { + return -1; + } + + /* change to calling user's real uid and gid, retaining supplemental + * groups and CAP_NET_ADMIN */ + if (capng_change_id(getuid(), getgid(), CAPNG_CLEAR_BOUNDING)) + { + return -1; + } + + return 0; +} +#endif + +int bridge_helper(const char* bridge, const int unixfd, const int use_vnet) +{ + struct ifreq ifr; +#ifndef SIOCBRADDIF + unsigned long ifargs[4]; +#endif + int ifindex; + int fd = -1, ctlfd = -1; + int mtu; + char iface[IFNAMSIZ]; + int ret = EXIT_SUCCESS; + +#ifdef CONFIG_LIBCAP_NG + /* if we're run from an suid binary, immediately drop privileges preserving + * cap_net_admin */ + if (geteuid() == 0 && getuid() != geteuid()) + { + if (drop_privileges() == -1) + { + fprintf(stderr, "failed to drop privileges\n"); + return 1; + } + } +#endif + + if (bridge == NULL || unixfd == -1) + { + usage(); + return EXIT_FAILURE; + } + if (strlen(bridge) >= IFNAMSIZ) + { + fprintf(stderr, "name `%s' too long: %zu\n", bridge, strlen(bridge)); + return EXIT_FAILURE; + } + + /* open a socket to use to control the network interfaces */ + ctlfd = socket(AF_INET, SOCK_STREAM, 0); + if (ctlfd == -1) + { + fprintf(stderr, "failed to open control socket: %s\n", strerror(errno)); + ret = EXIT_FAILURE; + goto cleanup; + } + + /* open the tap device */ + fd = open("/dev/net/tun", O_RDWR); + if (fd == -1) + { + fprintf(stderr, "failed to open /dev/net/tun: %s\n", strerror(errno)); + ret = EXIT_FAILURE; + goto cleanup; + } + + /* request a tap device, disable PI, and add vnet header support if + * requested and it's available. */ + prep_ifreq(&ifr, "tap%d"); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + if (use_vnet && has_vnet_hdr(fd)) + { + ifr.ifr_flags |= IFF_VNET_HDR; + } + + if (ioctl(fd, TUNSETIFF, &ifr) == -1) + { + fprintf(stderr, "failed to create tun device: %s\n", strerror(errno)); + ret = EXIT_FAILURE; + goto cleanup; + } + + /* save tap device name */ + snprintf(iface, sizeof(iface), "%s", ifr.ifr_name); + + /* get the mtu of the bridge */ + prep_ifreq(&ifr, bridge); + if (ioctl(ctlfd, SIOCGIFMTU, &ifr) == -1) + { + fprintf(stderr, "failed to get mtu of bridge `%s': %s\n", bridge, strerror(errno)); + ret = EXIT_FAILURE; + goto cleanup; + } + + /* save mtu */ + mtu = ifr.ifr_mtu; + + /* set the mtu of the interface based on the bridge */ + prep_ifreq(&ifr, iface); + ifr.ifr_mtu = mtu; + if (ioctl(ctlfd, SIOCSIFMTU, &ifr) == -1) + { + fprintf(stderr, "failed to set mtu of device `%s' to %d: %s\n", iface, mtu, strerror(errno)); + ret = EXIT_FAILURE; + goto cleanup; + } + + /* Linux uses the lowest enslaved MAC address as the MAC address of + * the bridge. Set MAC address to a high value so that it doesn't + * affect the MAC address of the bridge. + */ + if (ioctl(ctlfd, SIOCGIFHWADDR, &ifr) < 0) + { + fprintf(stderr, "failed to get MAC address of device `%s': %s\n", iface, strerror(errno)); + ret = EXIT_FAILURE; + goto cleanup; + } + ifr.ifr_hwaddr.sa_data[0] = (char)0xFE; + if (ioctl(ctlfd, SIOCSIFHWADDR, &ifr) < 0) + { + fprintf(stderr, "failed to set MAC address of device `%s': %s\n", iface, strerror(errno)); + ret = EXIT_FAILURE; + goto cleanup; + } + + /* add the interface to the bridge */ + prep_ifreq(&ifr, bridge); + ifindex = if_nametoindex(iface); +#ifndef SIOCBRADDIF + ifargs[0] = BRCTL_ADD_IF; + ifargs[1] = ifindex; + ifargs[2] = 0; + ifargs[3] = 0; + ifr.ifr_data = (void*)ifargs; + ret = ioctl(ctlfd, SIOCDEVPRIVATE, &ifr); +#else + ifr.ifr_ifindex = ifindex; + ret = ioctl(ctlfd, SIOCBRADDIF, &ifr); +#endif + if (ret == -1) + { + fprintf(stderr, "failed to add interface `%s' to bridge `%s': %s\n", iface, bridge, strerror(errno)); + ret = EXIT_FAILURE; + goto cleanup; + } + + /* bring the interface up */ + prep_ifreq(&ifr, iface); + if (ioctl(ctlfd, SIOCGIFFLAGS, &ifr) == -1) + { + fprintf(stderr, "failed to get interface flags for `%s': %s\n", iface, strerror(errno)); + ret = EXIT_FAILURE; + goto cleanup; + } + + ifr.ifr_flags |= IFF_UP; + if (ioctl(ctlfd, SIOCSIFFLAGS, &ifr) == -1) + { + fprintf(stderr, "failed to bring up interface `%s': %s\n", iface, strerror(errno)); + ret = EXIT_FAILURE; + goto cleanup; + } + + /* write fd to the domain socket */ + if (send_fd(unixfd, fd) == -1) + { + fprintf(stderr, "failed to write fd to unix socket: %s\n", strerror(errno)); + ret = EXIT_FAILURE; + goto cleanup; + } + + /* ... */ + + /* profit! */ + +cleanup: + if (fd >= 0) + { + close(fd); + } + if (ctlfd >= 0) + { + close(ctlfd); + } + + return ret; +} + +int main(int argc, char** argv) +{ + int use_vnet = 0; + const char* bridge = NULL; + int unixfd = -1; + int index; + + for (index = 1; index < argc; index++) + { + if (strcmp(argv[index], "--use-vnet") == 0) + { + use_vnet = 1; + } + else if (strncmp(argv[index], "--br=", 5) == 0) + { + bridge = &argv[index][5]; + } + else if (strncmp(argv[index], "--fd=", 5) == 0) + { + unixfd = atoi(&argv[index][5]); + } + else + { + usage(); + return EXIT_FAILURE; + } + } + + return bridge_helper(bridge, unixfd, use_vnet); +} From cceb27e3f193e4c7390102dc57624c884f219935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Pe=C3=B1aranda?= Date: Thu, 11 Apr 2024 15:18:12 -0300 Subject: [PATCH 06/19] [cmake] Build bridge helper on Linux. --- CMakeLists.txt | 4 +++- src/platform/backends/qemu/linux/CMakeLists.txt | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 18e47b7402..0a890e4b20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,7 +240,9 @@ else() add_definitions(-DMULTIPASS_COMPILER_GCC) endif() - add_compile_options(-Wextra -Wempty-body -Wformat-security -Winit-self -Warray-bounds -Wnon-virtual-dtor) + add_compile_options(-Wextra -Wempty-body -Wformat-security -Winit-self -Warray-bounds) + + string(APPEND CMAKE_CXX_FLAGS " -Wnon-virtual-dtor") if(NOT ${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "^arm") add_compile_options(-Wcast-align) diff --git a/src/platform/backends/qemu/linux/CMakeLists.txt b/src/platform/backends/qemu/linux/CMakeLists.txt index a3217f9e22..6bd58eaddb 100644 --- a/src/platform/backends/qemu/linux/CMakeLists.txt +++ b/src/platform/backends/qemu/linux/CMakeLists.txt @@ -27,3 +27,8 @@ target_link_libraries(qemu_platform_detail utils Qt6::Core) +add_executable(bridge_helper bridge_helper.c) + +install(TARGETS bridge_helper + DESTINATION bin + COMPONENT bridge_helper) From 50b6050940c734168471e9291149907733319da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Pe=C3=B1aranda?= Date: Fri, 12 Apr 2024 09:02:21 -0300 Subject: [PATCH 07/19] [qemu][linux] Add bridge helper to command line. --- .../qemu/linux/qemu_platform_detail_linux.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp index 332ab5eab4..8c5f05dc82 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -24,6 +24,7 @@ #include +#include #include namespace mp = multipass; @@ -192,6 +193,17 @@ QStringList mp::QemuPlatformDetail::vm_platform_args(const VirtualMachineDescrip << QString::fromStdString(fmt::format("tap,ifname={},script=no,downscript=no,model=virtio-net-pci,mac={}", tap_device_name, vm_desc.default_mac_address)); + auto bridge_helper_path = QCoreApplication::applicationDirPath() + "/bridge_helper"; + + for (const auto& extra_interface : vm_desc.extra_interfaces) + { + opts << "-nic" + << QString::fromStdString(fmt::format("bridge,br={},model=virtio-net-pci,mac={},helper={}", + extra_interface.id, + extra_interface.mac_address, + bridge_helper_path)); + } + return opts; } From 929028f40154bd011b2d6692dd16f4ae7b5bdac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Pe=C3=B1aranda?= Date: Mon, 15 Apr 2024 16:25:43 -0300 Subject: [PATCH 08/19] [apparmor] Let bridge helper run. --- src/platform/backends/qemu/qemu_vm_process_spec.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/backends/qemu/qemu_vm_process_spec.cpp b/src/platform/backends/qemu/qemu_vm_process_spec.cpp index 639d43b20a..4d9884c2f4 100644 --- a/src/platform/backends/qemu/qemu_vm_process_spec.cpp +++ b/src/platform/backends/qemu/qemu_vm_process_spec.cpp @@ -153,6 +153,9 @@ profile %1 flags=(attach_disconnected) { /{usr/,}bin/dd rmix, /{usr/,}bin/cat rmix, + # to execute bridge helper + %4/bin/bridge_helper, + # for restore /{usr/,}bin/bash rmix, From 2cdd2956f6aa3c83d6a5b8800305f34930da211e Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Fri, 5 Jul 2024 15:20:28 -0700 Subject: [PATCH 09/19] [qemu] wire up adding extra network interfaces --- .../backends/qemu/linux/qemu_platform_detail.h | 1 - .../qemu/linux/qemu_platform_detail_linux.cpp | 12 ------------ src/platform/backends/qemu/qemu_platform.h | 1 - src/platform/backends/qemu/qemu_virtual_machine.cpp | 2 +- tests/qemu/linux/test_qemu_platform_detail.cpp | 9 --------- tests/qemu/mock_qemu_platform.h | 1 - tests/qemu/test_qemu_backend.cpp | 2 -- 7 files changed, 1 insertion(+), 27 deletions(-) diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail.h b/src/platform/backends/qemu/linux/qemu_platform_detail.h index db67faf9c4..fed2fdd1da 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail.h +++ b/src/platform/backends/qemu/linux/qemu_platform_detail.h @@ -43,7 +43,6 @@ class QemuPlatformDetail : public QemuPlatform QStringList vm_platform_args(const VirtualMachineDescription& vm_desc) override; bool is_network_supported(const std::string& network_type) const override; bool needs_network_prep() const override; - void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) override; std::string create_bridge_with(const NetworkInterfaceInfo& interface) const override; private: diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp index 8c5f05dc82..2b7a2824ff 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -207,18 +207,6 @@ QStringList mp::QemuPlatformDetail::vm_platform_args(const VirtualMachineDescrip return opts; } -void mp::QemuPlatformDetail::add_network_interface(VirtualMachineDescription& desc, - const NetworkInterface& extra_interface) -{ - // TODO: Do not uncomment the following line when implementing bridging in Linux QEMU. Since implementing it would - // yield the same exact implementation than in macOS, we need to move the code out of here, and put it only once - // in src/platform/backends/qemu/qemu_virtual_machine.[h|cpp]. - // desc.extra_interfaces.push_back(net); - - // For the time being, just complain: - throw NotImplementedOnThisBackendException("networks"); -} - mp::QemuPlatform::UPtr mp::QemuPlatformFactory::make_qemu_platform(const Path& data_dir) const { return std::make_unique(data_dir); diff --git a/src/platform/backends/qemu/qemu_platform.h b/src/platform/backends/qemu/qemu_platform.h index 0e7f4a21dd..2adfd7baad 100644 --- a/src/platform/backends/qemu/qemu_platform.h +++ b/src/platform/backends/qemu/qemu_platform.h @@ -56,7 +56,6 @@ class QemuPlatform : private DisabledCopyMove }; virtual bool is_network_supported(const std::string& network_type) const = 0; virtual bool needs_network_prep() const = 0; - virtual void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) = 0; virtual std::string create_bridge_with(const NetworkInterfaceInfo& interface) const = 0; protected: diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 21979c00ed..b5cdae2935 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -649,7 +649,7 @@ void mp::QemuVirtualMachine::add_network_interface(int /* not used on this backe const std::string& default_mac_addr, const NetworkInterface& extra_interface) { - qemu_platform->add_network_interface(desc, extra_interface); + desc.extra_interfaces.push_back(extra_interface); add_extra_interface_to_instance_cloud_init(default_mac_addr, extra_interface); } diff --git a/tests/qemu/linux/test_qemu_platform_detail.cpp b/tests/qemu/linux/test_qemu_platform_detail.cpp index 7854160103..113e77cf3f 100644 --- a/tests/qemu/linux/test_qemu_platform_detail.cpp +++ b/tests/qemu/linux/test_qemu_platform_detail.cpp @@ -208,12 +208,3 @@ TEST_F(QemuPlatformDetail, writing_ipforward_file_failure_logs_expected_message) mp::QemuPlatformDetail qemu_platform_detail{data_dir.path()}; } - -TEST_F(QemuPlatformDetail, add_network_interface_throws) -{ - mp::QemuPlatformDetail qemu_platform_detail{data_dir.path()}; - - mp::VirtualMachineDescription desc; - mp::NetworkInterface net{"id", "52:54:00:98:76:54", true}; - EXPECT_THROW(qemu_platform_detail.add_network_interface(desc, net), mp::NotImplementedOnThisBackendException); -} diff --git a/tests/qemu/mock_qemu_platform.h b/tests/qemu/mock_qemu_platform.h index 08130a6a7a..4d9101aedf 100644 --- a/tests/qemu/mock_qemu_platform.h +++ b/tests/qemu/mock_qemu_platform.h @@ -44,7 +44,6 @@ struct MockQemuPlatform : public QemuPlatform MOCK_METHOD(QString, get_directory_name, (), (const, override)); MOCK_METHOD(bool, is_network_supported, (const std::string&), (const, override)); MOCK_METHOD(bool, needs_network_prep, (), (const override)); - MOCK_METHOD(void, add_network_interface, (VirtualMachineDescription&, const NetworkInterface&), (override)); MOCK_METHOD(std::string, create_bridge_with, (const NetworkInterfaceInfo&), (const, override)); }; diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp index 8857baa732..551eacab98 100644 --- a/tests/qemu/test_qemu_backend.cpp +++ b/tests/qemu/test_qemu_backend.cpp @@ -885,8 +885,6 @@ TEST_F(QemuBackend, addNetworkInterface) return std::move(mock_qemu_platform); }); - EXPECT_CALL(*mock_qemu_platform, add_network_interface(_, _)).Times(1); - const auto [mock_cloud_init_file_ops, _] = mpt::MockCloudInitFileOps::inject(); EXPECT_CALL(*mock_cloud_init_file_ops, add_extra_interface_to_cloud_init).Times(1); From 3f2454823dd4e714af2bd8dfcbe83f40d36e4a1c Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Fri, 5 Jul 2024 19:16:23 -0700 Subject: [PATCH 10/19] [qemu] align tests with bridged networks on linux --- tests/qemu/test_qemu_backend.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp index 551eacab98..3f61e43e0a 100644 --- a/tests/qemu/test_qemu_backend.cpp +++ b/tests/qemu/test_qemu_backend.cpp @@ -801,7 +801,7 @@ TEST_F(QemuBackend, createsQemuSnapshotsFromJsonFile) EXPECT_EQ(snapshot->get_parent(), parent); } -TEST_F(QemuBackend, lists_no_networks) +TEST_F(QemuBackend, networks_does_not_throw) { EXPECT_CALL(*mock_qemu_platform_factory, make_qemu_platform(_)).WillOnce([this](auto...) { return std::move(mock_qemu_platform); @@ -809,7 +809,7 @@ TEST_F(QemuBackend, lists_no_networks) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - EXPECT_THROW(backend.networks(), mp::NotImplementedOnThisBackendException); + EXPECT_NO_THROW(backend.networks()); } TEST_F(QemuBackend, remove_resources_for_calls_qemu_platform) From bd14874b4407fdea8626943dce553742f0705d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Pe=C3=B1aranda?= Date: Thu, 25 Apr 2024 15:02:35 -0300 Subject: [PATCH 11/19] [tests][apparmor] Check bridge helper is allowed. --- tests/qemu/test_qemu_vm_process_spec.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/qemu/test_qemu_vm_process_spec.cpp b/tests/qemu/test_qemu_vm_process_spec.cpp index b54213575f..50af4fa9ea 100644 --- a/tests/qemu/test_qemu_vm_process_spec.cpp +++ b/tests/qemu/test_qemu_vm_process_spec.cpp @@ -20,6 +20,7 @@ #include +#include #include #include #include @@ -186,3 +187,26 @@ TEST_F(TestQemuVMProcessSpec, apparmor_profile_not_running_as_snap_correct) EXPECT_TRUE(spec.apparmor_profile().contains("/usr{,/local}/share/{seabios,ovmf,qemu,qemu-efi}/* r,")); EXPECT_TRUE(spec.apparmor_profile().contains(" /usr/bin/qemu-system-")); // space wanted } + +TEST_F(TestQemuVMProcessSpec, apparmor_profile_lets_bridge_helper_run_in_snap) +{ + const QByteArray snap_name{"multipass"}; + QTemporaryDir snap_dir; + + mpt::SetEnvScope e("SNAP", snap_dir.path().toUtf8()); + mpt::SetEnvScope e2("SNAP_NAME", snap_name); + mp::QemuVMProcessSpec spec(desc, platform_args, mount_args, std::nullopt); + + EXPECT_TRUE(spec.apparmor_profile().contains(QString(" %1/bin/bridge_helper").arg(snap_dir.path()))); +} + +TEST_F(TestQemuVMProcessSpec, apparmor_profile_lets_bridge_helper_run_outside_snap) +{ + const QByteArray snap_name{"multipass"}; + + mpt::UnsetEnvScope e("SNAP"); + mpt::SetEnvScope e2("SNAP_NAME", snap_name); + mp::QemuVMProcessSpec spec(desc, platform_args, mount_args, std::nullopt); + + EXPECT_TRUE(spec.apparmor_profile().contains(" /bin/bridge_helper")); +} From e3eec68405a7f7c47a536f7c1f3ec3c18be10674 Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Tue, 9 Jul 2024 13:06:41 -0700 Subject: [PATCH 12/19] [qemu] extract network authorization to Linux qemu platform --- .../backends/qemu/linux/qemu_platform_detail.h | 1 + .../qemu/linux/qemu_platform_detail_linux.cpp | 12 ++++++++++++ src/platform/backends/qemu/qemu_platform.h | 1 + .../backends/qemu/qemu_virtual_machine_factory.cpp | 7 +------ tests/qemu/mock_qemu_platform.h | 1 + 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail.h b/src/platform/backends/qemu/linux/qemu_platform_detail.h index fed2fdd1da..e31e999591 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail.h +++ b/src/platform/backends/qemu/linux/qemu_platform_detail.h @@ -44,6 +44,7 @@ class QemuPlatformDetail : public QemuPlatform bool is_network_supported(const std::string& network_type) const override; bool needs_network_prep() const override; std::string create_bridge_with(const NetworkInterfaceInfo& interface) const override; + void set_authorization(std::vector& networks) override; private: const QString bridge_name; diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp index 2b7a2824ff..e09674e323 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -29,6 +30,7 @@ namespace mp = multipass; namespace mpl = multipass::logging; +namespace mpu = multipass::utils; namespace { @@ -222,6 +224,16 @@ bool mp::QemuPlatformDetail::needs_network_prep() const return true; } +void mp::QemuPlatformDetail::set_authorization(std::vector& networks) +{ + const auto& br_nomenclature = MP_PLATFORM.bridge_nomenclature(); + + for (auto& net : networks) + if (net.type == "ethernet" && net.needs_authorization && + mpu::find_bridge_with(networks, net.id, br_nomenclature)) + net.needs_authorization = false; +} + std::string mp::QemuPlatformDetail::create_bridge_with(const NetworkInterfaceInfo& interface) const { assert(interface.type == "ethernet"); diff --git a/src/platform/backends/qemu/qemu_platform.h b/src/platform/backends/qemu/qemu_platform.h index 2adfd7baad..17f2f103ee 100644 --- a/src/platform/backends/qemu/qemu_platform.h +++ b/src/platform/backends/qemu/qemu_platform.h @@ -57,6 +57,7 @@ class QemuPlatform : private DisabledCopyMove virtual bool is_network_supported(const std::string& network_type) const = 0; virtual bool needs_network_prep() const = 0; virtual std::string create_bridge_with(const NetworkInterfaceInfo& interface) const = 0; + virtual void set_authorization(std::vector& networks) = 0; protected: explicit QemuPlatform() = default; diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 5cbcf34e83..65ad872be8 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -30,7 +30,6 @@ namespace mp = multipass; namespace mpl = multipass::logging; -namespace mpu = multipass::utils; namespace { @@ -133,8 +132,6 @@ auto mp::QemuVirtualMachineFactory::networks() const -> std::vector ret; - const auto& br_nomenclature = MP_PLATFORM.bridge_nomenclature(); - for (const auto& ifs_info : platform_ifs_info) { const auto& info = ifs_info.second; @@ -144,9 +141,7 @@ auto mp::QemuVirtualMachineFactory::networks() const -> std::vectorset_authorization(ret); return ret; } diff --git a/tests/qemu/mock_qemu_platform.h b/tests/qemu/mock_qemu_platform.h index 4d9101aedf..1bf2b47eb9 100644 --- a/tests/qemu/mock_qemu_platform.h +++ b/tests/qemu/mock_qemu_platform.h @@ -45,6 +45,7 @@ struct MockQemuPlatform : public QemuPlatform MOCK_METHOD(bool, is_network_supported, (const std::string&), (const, override)); MOCK_METHOD(bool, needs_network_prep, (), (const override)); MOCK_METHOD(std::string, create_bridge_with, (const NetworkInterfaceInfo&), (const, override)); + MOCK_METHOD(void, set_authorization, (std::vector&), (override)); }; struct MockQemuPlatformFactory : public QemuPlatformFactory From 23f07dd114ada3cbf892d34f71366eb56906831e Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Thu, 11 Jul 2024 18:01:39 -0700 Subject: [PATCH 13/19] [qemu] prioritize boolean comparison before string comparison --- src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp index e09674e323..503c22068e 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -229,7 +229,7 @@ void mp::QemuPlatformDetail::set_authorization(std::vector const auto& br_nomenclature = MP_PLATFORM.bridge_nomenclature(); for (auto& net : networks) - if (net.type == "ethernet" && net.needs_authorization && + if (net.needs_authorization && net.type == "ethernet" && mpu::find_bridge_with(networks, net.id, br_nomenclature)) net.needs_authorization = false; } From 9a96d1efb9245cb144b54d51b8dcd7e5ca8b692e Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Thu, 11 Jul 2024 18:53:35 -0700 Subject: [PATCH 14/19] [qemu] add line coverage for factory and platform code --- .../qemu/linux/test_qemu_platform_detail.cpp | 35 ++++++++++++++++++- tests/qemu/test_qemu_backend.cpp | 23 ++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/tests/qemu/linux/test_qemu_platform_detail.cpp b/tests/qemu/linux/test_qemu_platform_detail.cpp index 113e77cf3f..1924f09fed 100644 --- a/tests/qemu/linux/test_qemu_platform_detail.cpp +++ b/tests/qemu/linux/test_qemu_platform_detail.cpp @@ -28,6 +28,8 @@ #include +#include + namespace mp = multipass; namespace mpl = multipass::logging; namespace mpt = multipass::test; @@ -132,8 +134,10 @@ TEST_F(QemuPlatformDetail, get_ip_for_returns_expected_info) TEST_F(QemuPlatformDetail, platform_args_generate_net_resources_removes_works_as_expected) { mp::VirtualMachineDescription vm_desc; + mp::NetworkInterface extra_interface{"br-en0", "52:54:00:98:76:54", true}; vm_desc.vm_name = "foo"; vm_desc.default_mac_address = hw_addr; + vm_desc.extra_interfaces = {extra_interface}; QString tap_name; @@ -162,7 +166,13 @@ TEST_F(QemuPlatformDetail, platform_args_generate_net_resources_removes_works_as #endif "--enable-kvm", "-cpu", "host", "-nic", QString::fromStdString(fmt::format("tap,ifname={},script=no,downscript=no,model=virtio-net-pci,mac={}", - tap_name, vm_desc.default_mac_address)) + tap_name, + vm_desc.default_mac_address)), + "-nic", + QString::fromStdString(fmt::format("bridge,br={},model=virtio-net-pci,mac={},helper={}", + extra_interface.id, + extra_interface.mac_address, + QCoreApplication::applicationDirPath() + "/bridge_helper")) }; EXPECT_THAT(platform_args, ElementsAreArray(expected_platform_args)); @@ -208,3 +218,26 @@ TEST_F(QemuPlatformDetail, writing_ipforward_file_failure_logs_expected_message) mp::QemuPlatformDetail qemu_platform_detail{data_dir.path()}; } + +TEST_F(QemuPlatformDetail, platformCorrectlySetsAuthorization) +{ + mp::QemuPlatformDetail qemu_platform_detail{data_dir.path()}; + std::vector networks{mp::NetworkInterfaceInfo{"en0", "ethernet", "", {}, true}, + mp::NetworkInterfaceInfo{"en1", "ethernet", "", {}, true}, + mp::NetworkInterfaceInfo{"br-en0", "bridge", "", {"en0"}, false}, + mp::NetworkInterfaceInfo{"mpbr0", "bridge", "", {}, false}}; + + qemu_platform_detail.set_authorization(networks); + + EXPECT_FALSE(networks[0].needs_authorization); + EXPECT_TRUE(networks[1].needs_authorization); +} + +TEST_F(QemuPlatformDetail, CreateBridgeWithCallsExpectedMethods) +{ + EXPECT_CALL(*mock_backend, create_bridge_with("en0")).WillOnce(Return("br-en0")); + + mp::QemuPlatformDetail qemu_platform_detail{data_dir.path()}; + + qemu_platform_detail.create_bridge_with(mp::NetworkInterfaceInfo{"en0", "ethernet", "", {}, true}); +} diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp index 3f61e43e0a..cdbe43fbad 100644 --- a/tests/qemu/test_qemu_backend.cpp +++ b/tests/qemu/test_qemu_backend.cpp @@ -21,6 +21,7 @@ #include "tests/mock_cloud_init_file_ops.h" #include "tests/mock_environment_helpers.h" #include "tests/mock_logger.h" +#include "tests/mock_platform.h" #include "tests/mock_process_factory.h" #include "tests/mock_snapshot.h" #include "tests/mock_status_monitor.h" @@ -809,6 +810,15 @@ TEST_F(QemuBackend, networks_does_not_throw) mp::QemuVirtualMachineFactory backend{data_dir.path()}; + auto [mock_platform, guard] = mpt::MockPlatform::inject(); + EXPECT_CALL(*mock_platform, get_network_interfaces_info) + .WillOnce(Return(std::map{ + {"lxdbr0", {"lxdbr0", "bridge", "gobbledygook"}}, + {"mpbr0", {"mpbr0", "bridge", "gobbledygook"}}, + {"virbr0", {"virbr0", "bridge", "gobbledygook"}}, + {"mpqemubr0", {"mpqemubr0", "bridge", "gobbledygook"}}, + {"enxe4b97a832426", {"enxe4b97a832426", "ethernet", "gobbledygook"}}})); + EXPECT_NO_THROW(backend.networks()); } @@ -895,6 +905,19 @@ TEST_F(QemuBackend, addNetworkInterface) EXPECT_NO_THROW(machine->add_network_interface(0, "", {"", "", true})); } +TEST_F(QemuBackend, createBridgeWithChecksWithQemuPlatform) +{ + EXPECT_CALL(*mock_qemu_platform_factory, make_qemu_platform(_)).WillOnce([this](auto...) { + return std::move(mock_qemu_platform); + }); + EXPECT_CALL(*mock_qemu_platform, needs_network_prep()).Times(1).WillRepeatedly(Return(true)); + + mp::QemuVirtualMachineFactory backend{data_dir.path()}; + + std::vector extra_interfaces{{"eth1", "52:54:00:00:00:00", true}}; + backend.prepare_networking(extra_interfaces); +} + TEST(QemuPlatform, base_qemu_platform_returns_expected_values) { mpt::MockQemuPlatform qemu_platform; From 793d271e52de5f567d12b1018167e191370e7815 Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Fri, 19 Jul 2024 15:20:37 -0700 Subject: [PATCH 15/19] [qemu] require authorization for non bridged networks --- .../qemu/linux/qemu_platform_detail_linux.cpp | 5 ++--- tests/qemu/linux/test_qemu_platform_detail.cpp | 11 ++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp index 503c22068e..f4f183ef8e 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -229,9 +229,8 @@ void mp::QemuPlatformDetail::set_authorization(std::vector const auto& br_nomenclature = MP_PLATFORM.bridge_nomenclature(); for (auto& net : networks) - if (net.needs_authorization && net.type == "ethernet" && - mpu::find_bridge_with(networks, net.id, br_nomenclature)) - net.needs_authorization = false; + if (net.type == "ethernet" && !mpu::find_bridge_with(networks, net.id, br_nomenclature)) + net.needs_authorization = true; } std::string mp::QemuPlatformDetail::create_bridge_with(const NetworkInterfaceInfo& interface) const diff --git a/tests/qemu/linux/test_qemu_platform_detail.cpp b/tests/qemu/linux/test_qemu_platform_detail.cpp index 1924f09fed..6ede824126 100644 --- a/tests/qemu/linux/test_qemu_platform_detail.cpp +++ b/tests/qemu/linux/test_qemu_platform_detail.cpp @@ -222,15 +222,16 @@ TEST_F(QemuPlatformDetail, writing_ipforward_file_failure_logs_expected_message) TEST_F(QemuPlatformDetail, platformCorrectlySetsAuthorization) { mp::QemuPlatformDetail qemu_platform_detail{data_dir.path()}; - std::vector networks{mp::NetworkInterfaceInfo{"en0", "ethernet", "", {}, true}, - mp::NetworkInterfaceInfo{"en1", "ethernet", "", {}, true}, - mp::NetworkInterfaceInfo{"br-en0", "bridge", "", {"en0"}, false}, + + std::vector networks{mp::NetworkInterfaceInfo{"br-en0", "bridge", "", {"en0"}, false}, mp::NetworkInterfaceInfo{"mpbr0", "bridge", "", {}, false}}; + const auto& bridged_network = networks.emplace_back(mp::NetworkInterfaceInfo{"en0", "ethernet", "", {}, false}); + const auto& non_bridged_network = networks.emplace_back(mp::NetworkInterfaceInfo{"en1", "ethernet", "", {}, false}); qemu_platform_detail.set_authorization(networks); - EXPECT_FALSE(networks[0].needs_authorization); - EXPECT_TRUE(networks[1].needs_authorization); + EXPECT_FALSE(bridged_network.needs_authorization); + EXPECT_TRUE(non_bridged_network.needs_authorization); } TEST_F(QemuPlatformDetail, CreateBridgeWithCallsExpectedMethods) From 5f25eba6178d9394ea1d5b2d955af6f7727d2e70 Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Wed, 14 Aug 2024 08:27:31 -0700 Subject: [PATCH 16/19] [make] use modern cmake conditional --- CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a890e4b20..033f9db493 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,9 +240,7 @@ else() add_definitions(-DMULTIPASS_COMPILER_GCC) endif() - add_compile_options(-Wextra -Wempty-body -Wformat-security -Winit-self -Warray-bounds) - - string(APPEND CMAKE_CXX_FLAGS " -Wnon-virtual-dtor") + add_compile_options(-Wextra -Wempty-body -Wformat-security -Winit-self -Warray-bounds $<$:-Wnon-virtual-dtor>) if(NOT ${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "^arm") add_compile_options(-Wcast-align) From 07250d306360e30544c0a6708c3822749ace9599 Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Wed, 14 Aug 2024 08:33:36 -0700 Subject: [PATCH 17/19] [qemu] improve directory pathing --- .../backends/qemu/linux/qemu_platform_detail_linux.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp index f4f183ef8e..ae4224d77e 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -195,7 +195,7 @@ QStringList mp::QemuPlatformDetail::vm_platform_args(const VirtualMachineDescrip << QString::fromStdString(fmt::format("tap,ifname={},script=no,downscript=no,model=virtio-net-pci,mac={}", tap_device_name, vm_desc.default_mac_address)); - auto bridge_helper_path = QCoreApplication::applicationDirPath() + "/bridge_helper"; + const auto bridge_helper_exec_path = QDir(QCoreApplication::applicationDirPath()).filePath("bridge_helper"); for (const auto& extra_interface : vm_desc.extra_interfaces) { @@ -203,7 +203,7 @@ QStringList mp::QemuPlatformDetail::vm_platform_args(const VirtualMachineDescrip << QString::fromStdString(fmt::format("bridge,br={},model=virtio-net-pci,mac={},helper={}", extra_interface.id, extra_interface.mac_address, - bridge_helper_path)); + bridge_helper_exec_path)); } return opts; From c9a3b6af88b361665f662c15cdd4967167ac61f6 Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Wed, 14 Aug 2024 10:44:01 -0700 Subject: [PATCH 18/19] [tests] refine qemu platform tests --- .../qemu/linux/test_qemu_platform_detail.cpp | 3 ++- tests/qemu/test_qemu_backend.cpp | 24 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/qemu/linux/test_qemu_platform_detail.cpp b/tests/qemu/linux/test_qemu_platform_detail.cpp index 6ede824126..f66aeb4323 100644 --- a/tests/qemu/linux/test_qemu_platform_detail.cpp +++ b/tests/qemu/linux/test_qemu_platform_detail.cpp @@ -240,5 +240,6 @@ TEST_F(QemuPlatformDetail, CreateBridgeWithCallsExpectedMethods) mp::QemuPlatformDetail qemu_platform_detail{data_dir.path()}; - qemu_platform_detail.create_bridge_with(mp::NetworkInterfaceInfo{"en0", "ethernet", "", {}, true}); + EXPECT_EQ(qemu_platform_detail.create_bridge_with(mp::NetworkInterfaceInfo{"en0", "ethernet", "", {}, true}), + "br-en0"); } diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp index cdbe43fbad..153431b387 100644 --- a/tests/qemu/test_qemu_backend.cpp +++ b/tests/qemu/test_qemu_backend.cpp @@ -802,24 +802,28 @@ TEST_F(QemuBackend, createsQemuSnapshotsFromJsonFile) EXPECT_EQ(snapshot->get_parent(), parent); } -TEST_F(QemuBackend, networks_does_not_throw) +TEST_F(QemuBackend, networks_returns_supported_networks) { + ON_CALL(*mock_qemu_platform, is_network_supported(_)).WillByDefault(Return(true)); + EXPECT_CALL(*mock_qemu_platform_factory, make_qemu_platform(_)).WillOnce([this](auto...) { return std::move(mock_qemu_platform); }); mp::QemuVirtualMachineFactory backend{data_dir.path()}; + const std::map networks{ + {"lxdbr0", {"lxdbr0", "bridge", "gobbledygook"}}, + {"mpbr0", {"mpbr0", "bridge", "gobbledygook"}}, + {"virbr0", {"virbr0", "bridge", "gobbledygook"}}, + {"mpqemubr0", {"mpqemubr0", "bridge", "gobbledygook"}}, + {"enxe4b97a832426", {"enxe4b97a832426", "ethernet", "gobbledygook"}}}; + auto [mock_platform, guard] = mpt::MockPlatform::inject(); - EXPECT_CALL(*mock_platform, get_network_interfaces_info) - .WillOnce(Return(std::map{ - {"lxdbr0", {"lxdbr0", "bridge", "gobbledygook"}}, - {"mpbr0", {"mpbr0", "bridge", "gobbledygook"}}, - {"virbr0", {"virbr0", "bridge", "gobbledygook"}}, - {"mpqemubr0", {"mpqemubr0", "bridge", "gobbledygook"}}, - {"enxe4b97a832426", {"enxe4b97a832426", "ethernet", "gobbledygook"}}})); + EXPECT_CALL(*mock_platform, get_network_interfaces_info).WillOnce(Return(networks)); - EXPECT_NO_THROW(backend.networks()); + auto supported_nets = backend.networks(); + EXPECT_EQ(supported_nets.size(), networks.size()); } TEST_F(QemuBackend, remove_resources_for_calls_qemu_platform) @@ -915,7 +919,7 @@ TEST_F(QemuBackend, createBridgeWithChecksWithQemuPlatform) mp::QemuVirtualMachineFactory backend{data_dir.path()}; std::vector extra_interfaces{{"eth1", "52:54:00:00:00:00", true}}; - backend.prepare_networking(extra_interfaces); + EXPECT_NO_THROW(backend.prepare_networking(extra_interfaces)); } TEST(QemuPlatform, base_qemu_platform_returns_expected_values) From 64f0649c972c4a9f2a60686fd89f1b4d36a6d74c Mon Sep 17 00:00:00 2001 From: Scott Harder Date: Mon, 26 Aug 2024 08:18:22 -0700 Subject: [PATCH 19/19] [cmake] use static variable for bridge_helper name --- src/platform/backends/qemu/linux/CMakeLists.txt | 8 ++++++-- .../backends/qemu/linux/qemu_platform_detail_linux.cpp | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/qemu/linux/CMakeLists.txt b/src/platform/backends/qemu/linux/CMakeLists.txt index 6bd58eaddb..394491d864 100644 --- a/src/platform/backends/qemu/linux/CMakeLists.txt +++ b/src/platform/backends/qemu/linux/CMakeLists.txt @@ -12,12 +12,16 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +set(BRIDGE_HELPER_EXEC_NAME "bridge_helper") + add_library(qemu_platform_detail dnsmasq_process_spec.cpp dnsmasq_server.cpp firewall_config.cpp qemu_platform_detail_linux.cpp) +target_compile_definitions(qemu_platform_detail PRIVATE BRIDGE_HELPER_EXEC_NAME_CPP="${BRIDGE_HELPER_EXEC_NAME}") + target_include_directories(qemu_platform_detail PRIVATE ../) target_link_libraries(qemu_platform_detail @@ -27,8 +31,8 @@ target_link_libraries(qemu_platform_detail utils Qt6::Core) -add_executable(bridge_helper bridge_helper.c) +add_executable(${BRIDGE_HELPER_EXEC_NAME} bridge_helper.c) -install(TARGETS bridge_helper +install(TARGETS ${BRIDGE_HELPER_EXEC_NAME} DESTINATION bin COMPONENT bridge_helper) diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp index ae4224d77e..ee76cf6150 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -195,7 +195,8 @@ QStringList mp::QemuPlatformDetail::vm_platform_args(const VirtualMachineDescrip << QString::fromStdString(fmt::format("tap,ifname={},script=no,downscript=no,model=virtio-net-pci,mac={}", tap_device_name, vm_desc.default_mac_address)); - const auto bridge_helper_exec_path = QDir(QCoreApplication::applicationDirPath()).filePath("bridge_helper"); + const auto bridge_helper_exec_path = + QDir(QCoreApplication::applicationDirPath()).filePath(BRIDGE_HELPER_EXEC_NAME_CPP); for (const auto& extra_interface : vm_desc.extra_interfaces) {