Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 39 additions & 23 deletions src/platform/backends/qemu/linux/dnsmasq_process_spec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,33 @@
namespace mp = multipass;
namespace mpu = multipass::utils;

namespace
{
[[nodiscard]] QStringList make_dnsmasq_subnet_args(const mp::SubnetList& subnets)
{
QStringList out{};
for (const auto& [bridge_name, subnet] : subnets)
{
const auto bridge_addr = mp::IPAddress{fmt::format("{}.1", subnet)};
const auto start_ip = mp::IPAddress{fmt::format("{}.2", subnet)};
const auto end_ip = mp::IPAddress{fmt::format("{}.254", subnet)};

out << QString("--interface=%1").arg(bridge_name)
<< QString("--listen-address=%1").arg(QString::fromStdString(bridge_addr.as_string()))
<< "--dhcp-range"
<< QString("%1,%2,infinite")
.arg(QString::fromStdString(start_ip.as_string()))
.arg(QString::fromStdString(end_ip.as_string()));
}

return out;
}
} // namespace

mp::DNSMasqProcessSpec::DNSMasqProcessSpec(const mp::Path& data_dir,
const QString& bridge_name,
const std::string& subnet,
const SubnetList& subnets,
const QString& conf_file_path)
: data_dir(data_dir), bridge_name(bridge_name), subnet{subnet}, conf_file_path{conf_file_path}
: data_dir(data_dir), subnets(subnets), conf_file_path{conf_file_path}
{
}

Expand All @@ -39,26 +61,20 @@ QString mp::DNSMasqProcessSpec::program() const

QStringList mp::DNSMasqProcessSpec::arguments() const
{
const auto bridge_addr = mp::IPAddress{fmt::format("{}.1", subnet)};
const auto start_ip = mp::IPAddress{fmt::format("{}.2", subnet)};
const auto end_ip = mp::IPAddress{fmt::format("{}.254", subnet)};

return QStringList()
<< "--keep-in-foreground"
<< "--strict-order"
<< "--bind-interfaces" << QString("--pid-file") << "--domain=multipass"
<< "--local=/multipass/"
<< "--except-interface=lo" << QString("--interface=%1").arg(bridge_name)
<< QString("--listen-address=%1").arg(QString::fromStdString(bridge_addr.as_string()))
<< "--dhcp-no-override"
<< "--dhcp-ignore-clid"
<< "--dhcp-authoritative" << QString("--dhcp-leasefile=%1/dnsmasq.leases").arg(data_dir)
<< QString("--dhcp-hostsfile=%1/dnsmasq.hosts").arg(data_dir) << "--dhcp-range"
<< QString("%1,%2,infinite")
.arg(QString::fromStdString(start_ip.as_string()))
.arg(QString::fromStdString(end_ip.as_string()))
// This is to prevent it trying to read /etc/dnsmasq.conf
<< QString("--conf-file=%1").arg(conf_file_path);
auto out = QStringList() << "--keep-in-foreground"
<< "--strict-order"
<< "--bind-interfaces" << QString("--pid-file") << "--domain=multipass"
<< "--local=/multipass/"
<< "--except-interface=lo"
<< "--dhcp-no-override"
<< "--dhcp-ignore-clid"
<< "--dhcp-authoritative"
<< QString("--dhcp-leasefile=%1/dnsmasq.leases").arg(data_dir)
<< QString("--dhcp-hostsfile=%1/dnsmasq.hosts").arg(data_dir)
// This is to prevent it trying to read /etc/dnsmasq.conf
<< QString("--conf-file=%1").arg(conf_file_path);

return out << make_dnsmasq_subnet_args(subnets);
}

mp::logging::Level mp::DNSMasqProcessSpec::error_log_level() const
Expand Down
9 changes: 4 additions & 5 deletions src/platform/backends/qemu/linux/dnsmasq_process_spec.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

#pragma once

#include <multipass/ip_address.h>
#include "dnsmasq_server.h"

#include <multipass/path.h>
#include <multipass/process/process_spec.h>

Expand All @@ -30,8 +31,7 @@ class DNSMasqProcessSpec : public ProcessSpec
{
public:
explicit DNSMasqProcessSpec(const Path& data_dir,
const QString& bridge_name,
const std::string& subnet,
const SubnetList& subnets,
const QString& conf_file_path);

QString program() const override;
Expand All @@ -42,8 +42,7 @@ class DNSMasqProcessSpec : public ProcessSpec

private:
const Path data_dir;
const QString bridge_name;
const std::string subnet;
const SubnetList subnets;
const QString conf_file_path;
};

Expand Down
24 changes: 8 additions & 16 deletions src/platform/backends/qemu/linux/dnsmasq_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,16 @@ namespace
constexpr auto immediate_wait = 100; // period to wait for immediate dnsmasq failures, in ms

auto make_dnsmasq_process(const mp::Path& data_dir,
const QString& bridge_name,
const std::string& subnet,
const mp::SubnetList& subnets,
const QString& conf_file_path)
{
auto process_spec =
std::make_unique<mp::DNSMasqProcessSpec>(data_dir, bridge_name, subnet, conf_file_path);
auto process_spec = std::make_unique<mp::DNSMasqProcessSpec>(data_dir, subnets, conf_file_path);
return MP_PROCFACTORY.create_process(std::move(process_spec));
}
} // namespace

mp::DNSMasqServer::DNSMasqServer(const Path& data_dir,
const QString& bridge_name,
const std::string& subnet)
: data_dir{data_dir},
bridge_name{bridge_name},
subnet{subnet},
conf_file{QDir(data_dir).absoluteFilePath("dnsmasq-XXXXXX.conf")}
mp::DNSMasqServer::DNSMasqServer(const Path& data_dir, const SubnetList& subnets)
: data_dir{data_dir}, conf_file{QDir(data_dir).absoluteFilePath("dnsmasq-XXXXXX.conf")}
{
conf_file.open();
conf_file.close();
Expand All @@ -63,7 +56,7 @@ mp::DNSMasqServer::DNSMasqServer(const Path& data_dir,
dnsmasq_hosts.open(QIODevice::WriteOnly);
}

dnsmasq_cmd = make_dnsmasq_process(data_dir, bridge_name, subnet, conf_file.fileName());
dnsmasq_cmd = make_dnsmasq_process(data_dir, subnets, conf_file.fileName());
start_dnsmasq();
}

Expand Down Expand Up @@ -106,7 +99,7 @@ std::optional<mp::IPAddress> mp::DNSMasqServer::get_ip_for(const std::string& hw
return std::nullopt;
}

void mp::DNSMasqServer::release_mac(const std::string& hw_addr)
void mp::DNSMasqServer::release_mac(const std::string& hw_addr, const QString& bridge_name)
{
auto ip = get_ip_for(hw_addr);
if (!ip)
Expand Down Expand Up @@ -207,8 +200,7 @@ void mp::DNSMasqServer::start_dnsmasq()

mp::DNSMasqServer::UPtr mp::DNSMasqServerFactory::make_dnsmasq_server(
const mp::Path& network_dir,
const QString& bridge_name,
const std::string& subnet) const
const SubnetList& subnets) const
{
return std::make_unique<mp::DNSMasqServer>(network_dir, bridge_name, subnet);
return std::make_unique<mp::DNSMasqServer>(network_dir, subnets);
}
11 changes: 5 additions & 6 deletions src/platform/backends/qemu/linux/dnsmasq_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,18 @@ namespace multipass
{
class Process;

using SubnetList = std::vector<std::pair<QString, std::string>>;

class DNSMasqServer : private DisabledCopyMove
{
public:
using UPtr = std::unique_ptr<DNSMasqServer>;

DNSMasqServer(const Path& data_dir, const QString& bridge_name, const std::string& subnet);
DNSMasqServer(const Path& data_dir, const SubnetList& subnets);
virtual ~DNSMasqServer(); // inherited by mock for testing

virtual std::optional<IPAddress> get_ip_for(const std::string& hw_addr);
virtual void release_mac(const std::string& hw_addr);
virtual void release_mac(const std::string& hw_addr, const QString& bridge_name);
virtual void check_dnsmasq_running();

protected:
Expand All @@ -51,8 +53,6 @@ class DNSMasqServer : private DisabledCopyMove
void start_dnsmasq();

const QString data_dir;
const QString bridge_name;
const std::string subnet;
std::unique_ptr<Process> dnsmasq_cmd;
QMetaObject::Connection finish_connection;
QTemporaryFile conf_file;
Expand All @@ -67,7 +67,6 @@ class DNSMasqServerFactory : public Singleton<DNSMasqServerFactory>
: Singleton<DNSMasqServerFactory>::Singleton{pass} {};

virtual DNSMasqServer::UPtr make_dnsmasq_server(const Path& network_dir,
const QString& bridge_name,
const std::string& subnet) const;
const SubnetList& subnets) const;
};
} // namespace multipass
24 changes: 20 additions & 4 deletions src/platform/backends/qemu/linux/qemu_platform_detail.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,27 @@ class QemuPlatformDetail : public QemuPlatform
void set_authorization(std::vector<NetworkInterfaceInfo>& networks) override;

private:
const QString bridge_name;
// explicitly naming DisabledCopyMove since the private one derived from QemuPlatform takes
// precedence in lookup
struct Subnet : private multipass::DisabledCopyMove
{
const QString bridge_name;
const std::string subnet;
FirewallConfig::UPtr firewall_config;

Subnet(const Path& network_dir, const std::string& name);
~Subnet();
};
using Subnets = std::unordered_map<std::string, Subnet>;

[[nodiscard]] static Subnets get_subnets(const Path& network_dir);

[[nodiscard]] static SubnetList get_subnets_list(const Subnets&);

const Path network_dir;
const std::string subnet;
const Subnets subnets;
DNSMasqServer::UPtr dnsmasq_server;
FirewallConfig::UPtr firewall_config;
std::unordered_map<std::string, std::pair<QString, std::string>> name_to_net_device_map;
std::unordered_map<std::string, std::tuple<QString, std::string, QString>>
name_to_net_device_map;
};
} // namespace multipass
78 changes: 59 additions & 19 deletions src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*
*/

#include "multipass/constants.h"
#include "qemu_platform_detail.h"

#include <multipass/file_ops.h>
Expand All @@ -35,7 +36,7 @@ namespace mpu = multipass::utils;
namespace
{
constexpr auto category = "qemu platform";
const QString multipass_bridge_name{"mpqemubr0"};
const QString multipass_bridge_name{"mpqemubr%1"};

// An interface name can only be 15 characters, so this generates a hash of the
// VM instance name with a "tap-" prefix and then truncates it.
Expand Down Expand Up @@ -103,14 +104,10 @@ void set_ip_forward()
}
}

mp::DNSMasqServer::UPtr init_nat_network(const mp::Path& network_dir,
const QString& bridge_name,
const std::string& subnet)
mp::DNSMasqServer::UPtr init_nat_network(const mp::Path& network_dir, const mp::SubnetList& subnets)
{
create_virtual_switch(subnet, bridge_name);
set_ip_forward();

return MP_DNSMASQ_SERVER_FACTORY.make_dnsmasq_server(network_dir, bridge_name, subnet);
return MP_DNSMASQ_SERVER_FACTORY.make_dnsmasq_server(network_dir, subnets);
}

void delete_virtual_switch(const QString& bridge_name)
Expand All @@ -122,24 +119,62 @@ void delete_virtual_switch(const QString& bridge_name)
}
} // namespace

mp::QemuPlatformDetail::QemuPlatformDetail(const mp::Path& data_dir)
: bridge_name{multipass_bridge_name},
network_dir{MP_UTILS.make_dir(QDir(data_dir), "network")},
mp::QemuPlatformDetail::Subnet::Subnet(const Path& network_dir, const std::string& name)
: bridge_name{multipass_bridge_name.arg(name.c_str())},
subnet{MP_BACKEND.get_subnet(network_dir, bridge_name)},
dnsmasq_server{init_nat_network(network_dir, bridge_name, subnet)},
firewall_config{MP_FIREWALL_CONFIG_FACTORY.make_firewall_config(bridge_name, subnet)}
{
create_virtual_switch(subnet, bridge_name);
}

mp::QemuPlatformDetail::Subnet::~Subnet()
{
delete_virtual_switch(bridge_name);
}

[[nodiscard]] mp::QemuPlatformDetail::Subnets mp::QemuPlatformDetail::get_subnets(
const Path& network_dir)
{
Subnets subnets{};
subnets.reserve(default_zone_names.size());

for (const auto& zone : default_zone_names)
{
subnets.emplace(std::piecewise_construct,
std::forward_as_tuple(zone),
std::forward_as_tuple(network_dir, zone));
}

return subnets;
}

[[nodiscard]] mp::SubnetList mp::QemuPlatformDetail::get_subnets_list(const Subnets& subnets)
{
SubnetList out{};
out.reserve(subnets.size());

for (const auto& [_, subnet] : subnets)
{
out.emplace_back(subnet.bridge_name, subnet.subnet);
}

return out;
}

mp::QemuPlatformDetail::QemuPlatformDetail(const mp::Path& data_dir)
: network_dir{MP_UTILS.make_dir(QDir(data_dir), "network")},
subnets{get_subnets(network_dir)},
dnsmasq_server{init_nat_network(network_dir, get_subnets_list(subnets))}
{
}

mp::QemuPlatformDetail::~QemuPlatformDetail()
{
for (const auto& it : name_to_net_device_map)
{
const auto& [tap_device_name, hw_addr] = it.second;
const auto& [tap_device_name, hw_addr, _] = it.second;
remove_tap_device(tap_device_name);
}

delete_virtual_switch(bridge_name);
}

std::optional<mp::IPAddress> mp::QemuPlatformDetail::get_ip_for(const std::string& hw_addr)
Expand All @@ -152,8 +187,8 @@ void mp::QemuPlatformDetail::remove_resources_for(const std::string& name)
auto it = name_to_net_device_map.find(name);
if (it != name_to_net_device_map.end())
{
const auto& [tap_device_name, hw_addr] = it->second;
dnsmasq_server->release_mac(hw_addr);
const auto& [tap_device_name, hw_addr, bridge_name] = it->second;
dnsmasq_server->release_mac(hw_addr, bridge_name);
remove_tap_device(tap_device_name);

name_to_net_device_map.erase(name);
Expand All @@ -166,17 +201,22 @@ void mp::QemuPlatformDetail::platform_health_check()
MP_BACKEND.check_if_kvm_is_in_use();

dnsmasq_server->check_dnsmasq_running();
firewall_config->verify_firewall_rules();
for (const auto& [_, subnet] : subnets)
{
subnet.firewall_config->verify_firewall_rules();
}
}

QStringList mp::QemuPlatformDetail::vm_platform_args(const VirtualMachineDescription& vm_desc)
{
// Configure and generate the args for the default network interface
auto tap_device_name = generate_tap_device_name(vm_desc.vm_name);
const QString& bridge_name = subnets.at(vm_desc.zone).bridge_name;
create_tap_device(tap_device_name, bridge_name);

name_to_net_device_map.emplace(vm_desc.vm_name,
std::make_pair(tap_device_name, vm_desc.default_mac_address));
name_to_net_device_map.emplace(
vm_desc.vm_name,
std::make_tuple(tap_device_name, vm_desc.default_mac_address, bridge_name));

QStringList opts;

Expand Down
Loading
Loading