diff --git a/CMakeLists.txt b/CMakeLists.txt index 18e47b7402..033f9db493 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,7 +240,7 @@ 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 $<$:-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..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,3 +31,8 @@ target_link_libraries(qemu_platform_detail utils Qt6::Core) +add_executable(${BRIDGE_HELPER_EXEC_NAME} bridge_helper.c) + +install(TARGETS ${BRIDGE_HELPER_EXEC_NAME} + DESTINATION bin + COMPONENT bridge_helper) 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); +} diff --git a/src/platform/backends/qemu/linux/qemu_platform_detail.h b/src/platform/backends/qemu/linux/qemu_platform_detail.h index 0eb3501df1..e31e999591 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail.h +++ b/src/platform/backends/qemu/linux/qemu_platform_detail.h @@ -41,8 +41,10 @@ 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; - void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) override; - void prepare_networking(std::vector& extra_interfaces) const override; + 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 5ca806e579..ee76cf6150 100644 --- a/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp +++ b/src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp @@ -20,14 +20,17 @@ #include #include #include +#include #include #include +#include #include namespace mp = multipass; namespace mpl = multipass::logging; +namespace mpu = multipass::utils; namespace { @@ -192,27 +195,47 @@ 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_EXEC_NAME_CPP); + + 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_exec_path)); + } + return opts; } -void mp::QemuPlatformDetail::add_network_interface(VirtualMachineDescription& desc, - const NetworkInterface& extra_interface) +mp::QemuPlatform::UPtr mp::QemuPlatformFactory::make_qemu_platform(const Path& data_dir) const +{ + return std::make_unique(data_dir); +} + +bool mp::QemuPlatformDetail::is_network_supported(const std::string& network_type) const { - // 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); + return network_type == "bridge" || network_type == "ethernet"; +} - // For the time being, just complain: - throw NotImplementedOnThisBackendException("networks"); +bool mp::QemuPlatformDetail::needs_network_prep() const +{ + return true; } -mp::QemuPlatform::UPtr mp::QemuPlatformFactory::make_qemu_platform(const Path& data_dir) const +void mp::QemuPlatformDetail::set_authorization(std::vector& networks) { - return std::make_unique(data_dir); + const auto& br_nomenclature = MP_PLATFORM.bridge_nomenclature(); + + for (auto& net : networks) + if (net.type == "ethernet" && !mpu::find_bridge_with(networks, net.id, br_nomenclature)) + net.needs_authorization = true; } -void mp::QemuPlatformDetail::prepare_networking(std::vector& /*extra_interfaces*/) const +std::string mp::QemuPlatformDetail::create_bridge_with(const NetworkInterfaceInfo& interface) const { - // Nothing to do here until we implement networking on this backend + 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 8840e06fef..17f2f103ee 100644 --- a/src/platform/backends/qemu/qemu_platform.h +++ b/src/platform/backends/qemu/qemu_platform.h @@ -54,12 +54,10 @@ class QemuPlatform : private DisabledCopyMove { return {}; }; - virtual std::vector networks() const - { - throw NotImplementedOnThisBackendException("networks"); - }; - virtual void add_network_interface(VirtualMachineDescription& desc, const NetworkInterface& extra_interface) = 0; - virtual void prepare_networking(std::vector& extra_interfaces) const = 0; + 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.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 5fd6524fe0..7d86053b43 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -680,7 +680,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/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index dfe964c36c..65ad872be8 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -129,10 +129,30 @@ 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; + 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); + } + + qemu_platform->set_authorization(ret); + + return ret; } void mp::QemuVirtualMachineFactory::prepare_networking(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) +{ + 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/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, diff --git a/tests/qemu/linux/test_qemu_platform_detail.cpp b/tests/qemu/linux/test_qemu_platform_detail.cpp index 7854160103..f66aeb4323 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)); @@ -209,11 +219,27 @@ 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) +TEST_F(QemuPlatformDetail, platformCorrectlySetsAuthorization) { 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); + 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(bridged_network.needs_authorization); + EXPECT_TRUE(non_bridged_network.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()}; + + EXPECT_EQ(qemu_platform_detail.create_bridge_with(mp::NetworkInterfaceInfo{"en0", "ethernet", "", {}, true}), + "br-en0"); } diff --git a/tests/qemu/mock_qemu_platform.h b/tests/qemu/mock_qemu_platform.h index 28b6f3beb0..1bf2b47eb9 100644 --- a/tests/qemu/mock_qemu_platform.h +++ b/tests/qemu/mock_qemu_platform.h @@ -42,8 +42,10 @@ 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(void, add_network_interface, (VirtualMachineDescription&, const NetworkInterface&), (override)); - MOCK_METHOD(void, prepare_networking, (std::vector&), (const, override)); + 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 diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp index 5d3b54a2ca..27afddc593 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" @@ -966,15 +967,28 @@ TEST_F(QemuBackend, createsQemuSnapshotsFromJsonFile) EXPECT_EQ(snapshot->get_parent(), parent); } -TEST_F(QemuBackend, lists_no_networks) +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()}; - EXPECT_THROW(backend.networks(), mp::NotImplementedOnThisBackendException); + 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(networks)); + + auto supported_nets = backend.networks(); + EXPECT_EQ(supported_nets.size(), networks.size()); } TEST_F(QemuBackend, remove_resources_for_calls_qemu_platform) @@ -1050,8 +1064,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); @@ -1062,6 +1074,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}}; + EXPECT_NO_THROW(backend.prepare_networking(extra_interfaces)); +} + TEST(QemuPlatform, base_qemu_platform_returns_expected_values) { mpt::MockQemuPlatform qemu_platform; 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")); +}